From cef470b05aa42193b357491f7f5dafc97862a994 Mon Sep 17 00:00:00 2001 From: Kristoff Kiefer Date: Fri, 6 Oct 2023 18:42:07 +0200 Subject: [PATCH] Added a POC for OpenID via BFF --- package-lock.json | 229 ++++++++++++++---- package.json | 6 + src/frontend/main.ts | 23 +- .../frontend/api/frontend.controller.ts | 27 ++- src/modules/frontend/frontend-api.module.ts | 23 +- src/modules/frontend/login.guard.ts | 11 + src/modules/frontend/openid.strategy.ts | 32 +++ src/modules/frontend/session.serializer.ts | 13 + 8 files changed, 300 insertions(+), 64 deletions(-) create mode 100644 src/modules/frontend/login.guard.ts create mode 100644 src/modules/frontend/openid.strategy.ts create mode 100644 src/modules/frontend/session.serializer.ts diff --git a/package-lock.json b/package-lock.json index 0e6adf3e3..35506e32f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@nestjs/common": "^9.0.0", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", + "@nestjs/passport": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^7.0.4", "@nestjs/terminus": "^9.0.0", @@ -27,10 +28,13 @@ "axios": "^1.5.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "express-session": "^1.17.3", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "nest-commander": "^3.11.0", "nest-keycloak-connect": "^1.9.2", + "openid-client": "^5.6.0", + "passport": "^0.6.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, @@ -41,9 +45,11 @@ "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.4.2", "@types/express": "^4.17.13", + "@types/express-session": "^1.17.8", "@types/jest": "^29.2.4", "@types/lodash-es": "^4.17.9", "@types/node": "^20.3.1", + "@types/passport": "^1.0.13", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.0.0", @@ -1961,6 +1967,15 @@ } } }, + "node_modules/@nestjs/passport": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-9.0.3.tgz", + "integrity": "sha512-HplSJaimEAz1IOZEu+pdJHHJhQyBOPAYWXYHfAPQvRqWtw4FJF1VXl1Qtk9dcXQX1eKytDtH+qBzNQc19GWNEg==", + "peerDependencies": { + "@nestjs/common": "^8.0.0 || ^9.0.0", + "passport": "^0.4.0 || ^0.5.0 || ^0.6.0" + } + }, "node_modules/@nestjs/platform-express": { "version": "9.4.3", "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-9.4.3.tgz", @@ -2376,6 +2391,15 @@ "@types/send": "*" } }, + "node_modules/@types/express-session": { + "version": "1.17.8", + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.17.8.tgz", + "integrity": "sha512-bFF7/3wOldMn+56XyFRGY9ZzCr3JWhNSP2ajMPgTlbZR6BQOCHdAbNA9W5dMBPgMywpIP4zkmhxP6Opm/NRYMQ==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.7.tgz", @@ -2480,6 +2504,15 @@ "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "dev": true }, + "node_modules/@types/passport": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.13.tgz", + "integrity": "sha512-XXURryL+EZAWtbQFOHX1eNB+RJwz5XMPPz1xrGpEKr2xUZCXM4NCPkHMtZQ3B2tTSG/1IRaAcTHjczRA4sSFCw==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.8", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.8.tgz", @@ -3775,16 +3808,6 @@ "node": ">=4" } }, - "node_modules/buildcheck": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", - "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", - "dev": true, - "optional": true, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -4286,21 +4309,6 @@ "node": ">=10" } }, - "node_modules/cpu-features": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.9.tgz", - "integrity": "sha512-AKjgn2rP2yJyfbepsmLfiYcmtNn/2eUvocUyM/09yB0YDiz39HteK/5/T4Onf0pmdYDMgkBoGvRLvEguzyL7wQ==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "dependencies": { - "buildcheck": "~0.0.6", - "nan": "^2.17.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/crc-32": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", @@ -5448,6 +5456,45 @@ "node": ">= 0.10.0" } }, + "node_modules/express-session": { + "version": "1.17.3", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.3.tgz", + "integrity": "sha512-4+otWXlShYlG1Ma+2Jnn+xgKUZTMJ5QD3YvfilX3AcocOAbIkVylSWEklzALe/+Pu4qV6TYBj5GwOBFfdKqLBw==", + "dependencies": { + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express-session/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/express/node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -5907,20 +5954,6 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -7427,6 +7460,14 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jose": { + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.2.tgz", + "integrity": "sha512-IY73F228OXRl9ar3jJagh7Vnuhj/GzBunPiZP13K0lOl7Am9SoWW3kEzq3MCllJMTtZqHTiDXQvoRd4U95aU6A==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -8006,13 +8047,6 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" }, - "node_modules/nan": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", - "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", - "dev": true, - "optional": true - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8171,6 +8205,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.12.3", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", @@ -8269,6 +8311,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -8280,6 +8330,14 @@ "node": ">= 0.8" } }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -8303,6 +8361,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/openid-client": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.0.tgz", + "integrity": "sha512-uFTkN/iqgKvSnmpVAS/T6SNThukRMBcmymTQ71Ngus1F60tdtKVap7zCrleocY+fogPtpmoxi5Q1YdrgYuTlkA==", + "dependencies": { + "jose": "^4.15.1", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -8455,6 +8543,31 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.6.0.tgz", + "integrity": "sha512-0fe+p3ZnrWRW74fe8+SvCyf4a3Pb2/h7gFkQ8yTJpAO50gDzlfjZUZTO1k5Eg9kUct22OxHLqDZoKUWRHOh9ug==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1", + "utils-merge": "^1.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -8534,6 +8647,11 @@ "node": ">=8" } }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -8967,6 +9085,14 @@ } ] }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -10587,6 +10713,17 @@ "node": ">=8" } }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unbox-primitive": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", diff --git a/package.json b/package.json index f37d738ba..1b59ffa8c 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@nestjs/common": "^9.0.0", "@nestjs/config": "^2.3.2", "@nestjs/core": "^9.0.0", + "@nestjs/passport": "^9.0.0", "@nestjs/platform-express": "^9.0.0", "@nestjs/swagger": "^7.0.4", "@nestjs/terminus": "^9.0.0", @@ -49,10 +50,13 @@ "axios": "^1.5.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", + "express-session": "^1.17.3", "lodash": "^4.17.21", "lodash-es": "^4.17.21", "nest-commander": "^3.11.0", "nest-keycloak-connect": "^1.9.2", + "openid-client": "^5.6.0", + "passport": "^0.6.0", "reflect-metadata": "^0.1.13", "rxjs": "^7.2.0" }, @@ -63,9 +67,11 @@ "@nestjs/schematics": "^9.0.0", "@nestjs/testing": "^9.4.2", "@types/express": "^4.17.13", + "@types/express-session": "^1.17.8", "@types/jest": "^29.2.4", "@types/lodash-es": "^4.17.9", "@types/node": "^20.3.1", + "@types/passport": "^1.0.13", "@types/supertest": "^2.0.11", "@typescript-eslint/eslint-plugin": "^5.13.0", "@typescript-eslint/parser": "^5.0.0", diff --git a/src/frontend/main.ts b/src/frontend/main.ts index 62591b231..df5d7d518 100644 --- a/src/frontend/main.ts +++ b/src/frontend/main.ts @@ -1,12 +1,12 @@ /* eslint-disable no-console */ -import { INestApplication } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { NestFactory } from '@nestjs/core'; -import { DocumentBuilder, OpenAPIObject, SwaggerModule } from '@nestjs/swagger'; - -import { FrontendConfig, ServerConfig } from '../shared/config/index.js'; -import { GlobalValidationPipe } from '../shared/validation/index.js'; -import { FrontendModule } from './frontend.module.js'; +import {INestApplication} from '@nestjs/common'; +import {ConfigService} from '@nestjs/config'; +import {NestFactory} from '@nestjs/core'; +import {DocumentBuilder, OpenAPIObject, SwaggerModule} from '@nestjs/swagger'; +import {FrontendConfig, ServerConfig} from '../shared/config/index.js'; +import {GlobalValidationPipe} from '../shared/validation/index.js'; +import {FrontendModule} from './frontend.module.js'; +import session from 'express-session'; async function bootstrap(): Promise { const app: INestApplication = await NestFactory.create(FrontendModule); @@ -20,6 +20,13 @@ async function bootstrap(): Promise { app.setGlobalPrefix('api', { exclude: ['health'], }); + app.use( + session({ + secret: 'my-secret', + resave: false, + saveUninitialized: false, + }), + ); SwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, swagger)); const configService: ConfigService = app.get(ConfigService); diff --git a/src/modules/frontend/api/frontend.controller.ts b/src/modules/frontend/api/frontend.controller.ts index d1d9ebc3b..0ed0be524 100644 --- a/src/modules/frontend/api/frontend.controller.ts +++ b/src/modules/frontend/api/frontend.controller.ts @@ -1,24 +1,35 @@ -import { Controller, Post } from '@nestjs/common'; +import { Controller, Get, Res, Session, UseGuards } from '@nestjs/common'; import { ApiAcceptedResponse, ApiTags } from '@nestjs/swagger'; -import { AuthenticatedUser, Public, Resource } from 'nest-keycloak-connect'; +import { Response } from 'express'; +import { LoginGuard } from '../login.guard.js'; @ApiTags('frontend') @Controller({ path: 'frontend' }) export class FrontendController { // Endpoints decorated with @Public are accessible to everyone - @Public() - @Resource('test') - @Post('login') + @UseGuards(LoginGuard) + @Get('login') @ApiAcceptedResponse({ description: 'The person was successfully logged in.' }) public login(): string { return 'Login!'; } // Endpoints without @Public decorator automatically verify user - @Post('logout') + @Get('logout') @ApiAcceptedResponse({ description: 'The person was successfully logged out.' }) - public logout(@AuthenticatedUser() user: unknown): string { + public logout(): string { // Can get logged in user with @AuthenticatedUser (technically any-type, is the JSON response from keycloak) - return `Logout! ${JSON.stringify(user)}`; + return 'Logout!'; + } + + @Get('callback') + @UseGuards(LoginGuard) + public callback(@Res() response: Response): void { + response.redirect('/api/frontend/loginInfo'); + } + + @Get('loginInfo') + public loginInfo(@Session() session: { passport: object }): string { + return JSON.stringify(session.passport) } } diff --git a/src/modules/frontend/frontend-api.module.ts b/src/modules/frontend/frontend-api.module.ts index 761ce7aeb..947bf0b6e 100644 --- a/src/modules/frontend/frontend-api.module.ts +++ b/src/modules/frontend/frontend-api.module.ts @@ -1,9 +1,28 @@ import { Module } from '@nestjs/common'; import { FrontendController } from './api/frontend.controller.js'; +import { OpenidStrategy } from './openid.strategy.js'; +import { Issuer } from 'openid-client'; +import { PassportModule } from '@nestjs/passport'; +import {SessionSerializer} from "./session.serializer.js"; @Module({ - imports: [], - providers: [], + imports: [PassportModule.register({ session: true, defaultStrategy: 'openid' })], + providers: [ + { + provide: OpenidStrategy, + useFactory: async (): Promise => { + const TrustIssuer = await Issuer.discover( + 'https://keycloak.dev.spsh.dbildungsplattform.de/realms/master', + ); + const client = new TrustIssuer.Client({ + client_id: 'spsh', + client_secret: 'YDp6fYkbUcj4ZkyAOnbAHGQ9O72htc5M', + }); + return new OpenidStrategy(client); + }, + }, + SessionSerializer + ], controllers: [FrontendController], }) export class FrontendApiModule {} diff --git a/src/modules/frontend/login.guard.ts b/src/modules/frontend/login.guard.ts new file mode 100644 index 000000000..34b99f4c9 --- /dev/null +++ b/src/modules/frontend/login.guard.ts @@ -0,0 +1,11 @@ +import { ExecutionContext, Injectable } from '@nestjs/common'; +import { AuthGuard } from '@nestjs/passport'; + +@Injectable() +export class LoginGuard extends AuthGuard('openid') { + public override async canActivate(context: ExecutionContext): Promise { + const result = (await super.canActivate(context)) as boolean; + await super.logIn(context.switchToHttp().getRequest()); + return result; + } +} diff --git a/src/modules/frontend/openid.strategy.ts b/src/modules/frontend/openid.strategy.ts new file mode 100644 index 000000000..fa9da9a1a --- /dev/null +++ b/src/modules/frontend/openid.strategy.ts @@ -0,0 +1,32 @@ +import { PassportStrategy } from '@nestjs/passport'; +import { AuthorizationParameters, Client, Strategy, StrategyOptions, TokenSet, UserinfoResponse } from 'openid-client'; +import { UnauthorizedException } from '@nestjs/common'; + +export class OpenidStrategy extends PassportStrategy(Strategy, 'openid') { + public constructor(private client: Client) { + super({ + client: client, + usePKCE: true, + params: { redirect_uri: 'http://127.0.0.1:9091/api/frontend/callback' }, + } as StrategyOptions); + } + + public async validate(tokenset: TokenSet): Promise { + const userinfo: UserinfoResponse = await this.client.userinfo(tokenset); + + try { + const idToken = tokenset.id_token; + const accessToken = tokenset.access_token; + const refreshToken = tokenset.refresh_token; + const user = { + id_token: idToken, + access_token: accessToken, + refresh_token: refreshToken, + userinfo, + }; + return user; + } catch (err) { + throw new UnauthorizedException(); + } + } +} diff --git a/src/modules/frontend/session.serializer.ts b/src/modules/frontend/session.serializer.ts new file mode 100644 index 000000000..b15cdf28f --- /dev/null +++ b/src/modules/frontend/session.serializer.ts @@ -0,0 +1,13 @@ +import { PassportSerializer } from '@nestjs/passport'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class SessionSerializer extends PassportSerializer { + public serializeUser(user: unknown, done: (err: Error | null, user: unknown) => void): void { + done(null, user); + } + + public deserializeUser(payload: string, done: (err: Error | null, payload: string) => void): void { + done(null, payload); + } +}