From 36a6a98f18b3b19d0285cede635d2d3add19a837 Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 4 Nov 2023 17:21:35 +1100 Subject: [PATCH 1/2] getting user data works --- client/src/components/navbar/Navbar.tsx | 50 +++++++++++++++++++- client/src/interfaces/Users.ts | 10 ++++ server/src/auth/auth.controller.ts | 62 +++++++++++++------------ server/src/auth/auth.service.ts | 2 +- server/src/auth/oidc.strategy.ts | 33 +++++++++---- server/src/auth/session.serializer.ts | 7 ++- server/src/main.ts | 40 ++++++++-------- 7 files changed, 140 insertions(+), 64 deletions(-) create mode 100644 client/src/interfaces/Users.ts diff --git a/client/src/components/navbar/Navbar.tsx b/client/src/components/navbar/Navbar.tsx index 8d1187d21..728838b97 100644 --- a/client/src/components/navbar/Navbar.tsx +++ b/client/src/components/navbar/Navbar.tsx @@ -1,7 +1,7 @@ import { Description, Info, Security, Settings as SettingsIcon } from '@mui/icons-material'; -import { AppBar, Toolbar, Typography, useMediaQuery, useTheme } from '@mui/material'; +import { AppBar, Button, Toolbar, Typography, useMediaQuery, useTheme } from '@mui/material'; import { styled } from '@mui/system'; -import React, { useContext, useState } from 'react'; +import React, { useContext, useEffect, useState } from 'react'; import notanglesLogoGif from '../../assets/notangles.gif'; import notanglesLogo from '../../assets/notangles_1.png'; @@ -12,6 +12,7 @@ import Changelog from './Changelog'; import CustomModal from './CustomModal'; import Privacy from './Privacy'; import Settings from './Settings'; +import { User } from '../../interfaces/Users'; const LogoImg = styled('img')` height: 46px; @@ -51,8 +52,44 @@ const Weak = styled('span')` const Navbar: React.FC = () => { const [currLogo, setCurrLogo] = useState(notanglesLogo); const { term, termName, year } = useContext(AppContext); + const userData = {}; + const [user, setUser] = useState(userData); const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('sm')); + useEffect(() => { + async function runAsync() { + try { + const response = await fetch('http://localhost:3001/api/auth/user', { + credentials: 'include', + }); + const userResponse = await response.text(); + console.log(userResponse); + if (userResponse !== '') { + console.log(userResponse); + setUser(JSON.parse(userResponse)); + } else { + setUser({}); + } + } catch (error) { + console.log(error); + } + } + // Execute the created function directly + runAsync(); + // https://stackoverflow.com/a/55854902/1098564 + // eslint-disable-next-line + }, []); + const login = () => { + window.location.replace('http://localhost:3001/api/auth/login'); + }; + const logout = () => { + window.location.replace('http://localhost:3001/api/auth/logout'); + setUser({}); + }; + // https://stackoverflow.com/a/32108184/1098564 + const isEmpty = (obj: Object) => { + return Object.keys(obj).length === 0 && obj.constructor === Object; + }; return ( @@ -82,6 +119,15 @@ const Navbar: React.FC = () => { content={} /> } description={'Settings'} content={} /> + {isEmpty(user) ? ( + + ) : ( + + )} diff --git a/client/src/interfaces/Users.ts b/client/src/interfaces/Users.ts new file mode 100644 index 000000000..01819d653 --- /dev/null +++ b/client/src/interfaces/Users.ts @@ -0,0 +1,10 @@ +export interface User { + id_token?: string; + access_token?: string; + refresh_token?: string; + userinfo?: UserInfo; +} + +export interface UserInfo { + sub?: string; +} diff --git a/server/src/auth/auth.controller.ts b/server/src/auth/auth.controller.ts index a7bd1430d..605a1a86c 100644 --- a/server/src/auth/auth.controller.ts +++ b/server/src/auth/auth.controller.ts @@ -1,52 +1,54 @@ -import { - Controller, - Get, - Request, - Res, - UseGuards, -} from '@nestjs/common'; +import { Controller, Get, Post, Request, Res, UseGuards } from '@nestjs/common'; import { Response } from 'express'; import { LoginGuard } from './login.guard'; import { Issuer } from 'openid-client'; import { AuthDto } from './dtos'; +import { log } from 'console'; +const REDIRECT_LINK = 'http://localhost:5173'; -@Controller() +@Controller('auth') export class AuthController { - @UseGuards(LoginGuard) @Get('/login') login() {} @Get('/user') - user(@Request() req) { - return req.user + user(@Request() req, @Res() res: Response) { + return res.json(req.user); } - + @UseGuards(LoginGuard) - @Get('/callback') + @Get('/callback/csesoc') loginCallback(@Res() res: Response) { - res.redirect('/'); + res.redirect(REDIRECT_LINK); } - + @Get('/logout') async logout(@Request() req, @Res() res: Response) { const id_token = req.user ? req.user.id_token : undefined; - req.logout(); - req.session.destroy(async (error: any) => { - const TrustIssuer = await Issuer.discover(`${process.env.OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER}/.well-known/openid-configuration`); - const end_session_endpoint = TrustIssuer.metadata.end_session_endpoint; - if (end_session_endpoint) { - res.redirect(end_session_endpoint + - '?post_logout_redirect_uri=' + process.env.OAUTH2_CLIENT_REGISTRATION_LOGIN_POST_LOGOUT_REDIRECT_URI + - (id_token ? '&id_token_hint=' + id_token : '')); - } else { - res.redirect('/') - } - }) - } -} - + const TrustIssuer = await Issuer.discover( + `${process.env.OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER}/.well-known/openid-configuration`, + ); + if (!id_token || !TrustIssuer) { + return res.redirect(REDIRECT_LINK); + } + req.logout((err) => { + req.session.destroy(async (error: any) => { + const end_session_endpoint = TrustIssuer.metadata.end_session_endpoint; + if (end_session_endpoint) { + res.redirect( + end_session_endpoint + + '?post_logout_redirect_uri=' + + process.env + .OAUTH2_CLIENT_REGISTRATION_LOGIN_POST_LOGOUT_REDIRECT_URI + + (id_token ? '&id_token_hint=' + id_token : ''), + ); + } + }); + }); + } +} diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index a41c649f9..7c9295b94 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -1,4 +1,4 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Res } from '@nestjs/common'; @Injectable() export class AuthService {} diff --git a/server/src/auth/oidc.strategy.ts b/server/src/auth/oidc.strategy.ts index 6f27d0821..8fbcc0b6d 100644 --- a/server/src/auth/oidc.strategy.ts +++ b/server/src/auth/oidc.strategy.ts @@ -1,11 +1,19 @@ // auth/oidc.strategy.ts import { UnauthorizedException } from '@nestjs/common'; import { PassportStrategy } from '@nestjs/passport'; -import { Strategy, Client, UserinfoResponse, TokenSet, Issuer } from 'openid-client'; +import { + Strategy, + Client, + UserinfoResponse, + TokenSet, + Issuer, +} from 'openid-client'; import { AuthService } from './auth.service'; export const buildOpenIdClient = async () => { - const TrustIssuer = await Issuer.discover(`${process.env.OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER}/.well-known/openid-configuration`); + const TrustIssuer = await Issuer.discover( + `${process.env.OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER}/.well-known/openid-configuration`, + ); const client = new TrustIssuer.Client({ client_id: process.env.OAUTH2_CLIENT_REGISTRATION_LOGIN_CLIENT_ID, client_secret: process.env.OAUTH2_CLIENT_REGISTRATION_LOGIN_CLIENT_SECRET, @@ -16,11 +24,15 @@ export const buildOpenIdClient = async () => { export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') { client: Client; - constructor(private readonly authService: AuthService, client: Client) { + constructor( + private readonly authService: AuthService, + client: Client, + ) { super({ client: client, params: { - redirect_uris: process.env.OAUTH2_CLIENT_REGISTRATION_LOGIN_REDIRECT_URI, + redirect_uris: + process.env.OAUTH2_CLIENT_REGISTRATION_LOGIN_REDIRECT_URI, scope: process.env.OAUTH2_CLIENT_REGISTRATION_LOGIN_SCOPE, }, passReqToCallback: false, @@ -32,20 +44,21 @@ export class OidcStrategy extends PassportStrategy(Strategy, 'oidc') { async validate(tokenset: TokenSet): Promise { const userinfo: UserinfoResponse = await this.client.userinfo(tokenset); - console.log("test"); try { - const id_token = tokenset.id_token - const access_token = tokenset.access_token - const refresh_token = tokenset.refresh_token + console.log(tokenset); + const id_token = tokenset.id_token; + const access_token = tokenset.access_token; + const refresh_token = tokenset.refresh_token; const user = { id_token, access_token, refresh_token, userinfo, - } + }; + console.log(user); return user; } catch (err) { throw new UnauthorizedException(); } } -} \ No newline at end of file +} diff --git a/server/src/auth/session.serializer.ts b/server/src/auth/session.serializer.ts index 10d5218c6..664f77777 100644 --- a/server/src/auth/session.serializer.ts +++ b/server/src/auth/session.serializer.ts @@ -5,7 +5,10 @@ export class SessionSerializer extends PassportSerializer { serializeUser(user: any, done: (err: Error, user: any) => void): any { done(null, user); } - deserializeUser(payload: any, done: (err: Error, payload: string) => void): any { + deserializeUser( + payload: any, + done: (err: Error, payload: string) => void, + ): any { done(null, payload); } -} \ No newline at end of file +} diff --git a/server/src/main.ts b/server/src/main.ts index 6e2f09b2b..5d5859adb 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -6,33 +6,35 @@ import * as session from 'express-session'; import * as passport from 'passport'; import { PrismaClient } from '@prisma/client'; - async function bootstrap() { const app = await NestFactory.create(AppModule); app.setGlobalPrefix('api'); - app.enableCors(); + app.enableCors({ + origin: 'http://localhost:5173', // Replace with your frontend domain + credentials: true, // Allow credentials (e.g., cookies) to be sent with the request + }); app.useGlobalPipes(new ValidationPipe()); - app.use(session ({ - store: new PrismaSessionStore( - new PrismaClient(), - { - checkPeriod: 2 * 60 * 1000, //ms + app.use( + session({ + store: new PrismaSessionStore(new PrismaClient(), { + checkPeriod: 2 * 60 * 1000, //ms dbRecordIdIsSessionId: true, dbRecordIdFunction: undefined, - } - ), // where session will be stored - secret: process.env.SESSION_SECRET, // to sign session id - resave: false, - saveUninitialized: false, - rolling: true, // keep session alive - cookie: { - maxAge: 30 * 60 * 1000, // session expires in 1hr, refreshed by `rolling: true` option. - httpOnly: true, // so that cookie can't be accessed via client-side script - } - })); + }), // where session will be stored + secret: process.env.SESSION_SECRET, // to sign session id + resave: false, + saveUninitialized: false, + + rolling: true, // keep session alive + cookie: { + secure: false, + maxAge: 30 * 60 * 1000, // session expires in 1hr, refreshed by `rolling: true` option. + httpOnly: true, // so that cookie can't be accessed via client-side script + }, + }), + ); app.use(passport.initialize()); app.use(passport.session()); - await app.listen(3001); // reminder to change it back to 3001 } From cef805180726aea9734cc60a6a7a012af0284e5b Mon Sep 17 00:00:00 2001 From: Ray Date: Sat, 4 Nov 2023 22:40:14 +1100 Subject: [PATCH 2/2] oidc is cooked --- client/src/components/navbar/Navbar.tsx | 15 +++++++-- client/src/interfaces/Users.ts | 9 +---- server/src/auth/auth.controller.ts | 36 ++++++-------------- server/src/auth/auth.service.ts | 44 +++++++++++++++++++++++-- server/src/constants.ts | 4 +++ 5 files changed, 69 insertions(+), 39 deletions(-) create mode 100644 server/src/constants.ts diff --git a/client/src/components/navbar/Navbar.tsx b/client/src/components/navbar/Navbar.tsx index 728838b97..d1c56b015 100644 --- a/client/src/components/navbar/Navbar.tsx +++ b/client/src/components/navbar/Navbar.tsx @@ -82,8 +82,16 @@ const Navbar: React.FC = () => { const login = () => { window.location.replace('http://localhost:3001/api/auth/login'); }; - const logout = () => { - window.location.replace('http://localhost:3001/api/auth/logout'); + const logout = async () => { + // window.location.replace('http://localhost:3001/api/auth/logout'); + try { + const response = await fetch('http://localhost:3001/api/auth/logout', { + credentials: 'include', + }); + } catch (error) { + console.log(error); + } + window.location.replace('http://localhost:5173'); setUser({}); }; // https://stackoverflow.com/a/32108184/1098564 @@ -105,6 +113,7 @@ const Navbar: React.FC = () => { Notangles {isMobile ? term : termName.concat(', ', year)} + } @@ -125,7 +134,7 @@ const Navbar: React.FC = () => { ) : ( )} diff --git a/client/src/interfaces/Users.ts b/client/src/interfaces/Users.ts index 01819d653..5fa304c07 100644 --- a/client/src/interfaces/Users.ts +++ b/client/src/interfaces/Users.ts @@ -1,10 +1,3 @@ export interface User { - id_token?: string; - access_token?: string; - refresh_token?: string; - userinfo?: UserInfo; -} - -export interface UserInfo { - sub?: string; + zid: string; } diff --git a/server/src/auth/auth.controller.ts b/server/src/auth/auth.controller.ts index 605a1a86c..bf01dd888 100644 --- a/server/src/auth/auth.controller.ts +++ b/server/src/auth/auth.controller.ts @@ -1,20 +1,27 @@ -import { Controller, Get, Post, Request, Res, UseGuards } from '@nestjs/common'; +import { Controller, Get, Request, Res, UseGuards } from '@nestjs/common'; import { Response } from 'express'; import { LoginGuard } from './login.guard'; import { Issuer } from 'openid-client'; import { AuthDto } from './dtos'; import { log } from 'console'; -const REDIRECT_LINK = 'http://localhost:5173'; +import { REDIRECT_LINK } from 'src/constants'; +import { AuthService } from './auth.service'; @Controller('auth') export class AuthController { + constructor(private authService: AuthService) {} + @UseGuards(LoginGuard) @Get('/login') login() {} @Get('/user') user(@Request() req, @Res() res: Response) { + if (req.user) { + return res.json(req.user.userinfo.sub); + } + return res.json(req.user); } @@ -26,29 +33,6 @@ export class AuthController { @Get('/logout') async logout(@Request() req, @Res() res: Response) { - const id_token = req.user ? req.user.id_token : undefined; - - const TrustIssuer = await Issuer.discover( - `${process.env.OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER}/.well-known/openid-configuration`, - ); - - if (!id_token || !TrustIssuer) { - return res.redirect(REDIRECT_LINK); - } - - req.logout((err) => { - req.session.destroy(async (error: any) => { - const end_session_endpoint = TrustIssuer.metadata.end_session_endpoint; - if (end_session_endpoint) { - res.redirect( - end_session_endpoint + - '?post_logout_redirect_uri=' + - process.env - .OAUTH2_CLIENT_REGISTRATION_LOGIN_POST_LOGOUT_REDIRECT_URI + - (id_token ? '&id_token_hint=' + id_token : ''), - ); - } - }); - }); + this.authService.logout(req, res); } } diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index 7c9295b94..93c8caa21 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -1,4 +1,44 @@ -import { Injectable, Res } from '@nestjs/common'; +import { + Controller, + Get, + Injectable, + Request, + Res, + UseGuards, +} from '@nestjs/common'; +import { Response } from 'express'; +import { LoginGuard } from './login.guard'; +import { Issuer } from 'openid-client'; +import { AuthDto } from './dtos'; +import { log } from 'console'; +import { REDIRECT_LINK } from 'src/constants'; @Injectable() -export class AuthService {} +export class AuthService { + async logout(@Request() req, @Res() res: Response): Promise { + const id_token = req.user ? req.user.id_token : undefined; + + const TrustIssuer = await Issuer.discover( + `${process.env.OAUTH2_CLIENT_PROVIDER_OIDC_ISSUER}/.well-known/openid-configuration`, + ); + + if (!id_token || !TrustIssuer) { + return res.redirect(REDIRECT_LINK); + } + + req.logout((err) => { + req.session.destroy(async (error: any) => { + const end_session_endpoint = TrustIssuer.metadata.end_session_endpoint; + if (end_session_endpoint) { + res.redirect( + end_session_endpoint + + '?post_logout_redirect_uri=' + + process.env + .OAUTH2_CLIENT_REGISTRATION_LOGIN_POST_LOGOUT_REDIRECT_URI + + (id_token ? '&id_token_hint=' + id_token : ''), + ); + } + }); + }); + } +} diff --git a/server/src/constants.ts b/server/src/constants.ts new file mode 100644 index 000000000..8860077f5 --- /dev/null +++ b/server/src/constants.ts @@ -0,0 +1,4 @@ +export const REDIRECT_LINK = 'http://localhost:5173'; +export interface User { + zid: string; +}