feat(Oauth2): implement full Oauth2 authentication handshake with Authentik IdP. Authorization (authentik-auth.service) is disconnected for now.
This commit is contained in:
parent
2feac880e3
commit
75910e377d
|
|
@ -1386,6 +1386,34 @@
|
||||||
"Timesheets"
|
"Timesheets"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/auth/login": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "AuthController_login",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Auth"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/auth/callback": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "AuthController_loginCallback",
|
||||||
|
"parameters": [],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"Auth"
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"info": {
|
"info": {
|
||||||
|
|
|
||||||
89
package-lock.json
generated
89
package-lock.json
generated
|
|
@ -19,6 +19,7 @@
|
||||||
"@prisma/client": "^6.11.1",
|
"@prisma/client": "^6.11.1",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.2",
|
"class-validator": "^0.14.2",
|
||||||
|
"express-session": "^1.18.2",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"passport-openidconnect": "^0.1.2",
|
"passport-openidconnect": "^0.1.2",
|
||||||
|
|
@ -34,6 +35,7 @@
|
||||||
"@swc/cli": "^0.6.0",
|
"@swc/cli": "^0.6.0",
|
||||||
"@swc/core": "^1.10.7",
|
"@swc/core": "^1.10.7",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/express-session": "^1.18.2",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
|
|
@ -3268,6 +3270,16 @@
|
||||||
"@types/send": "*"
|
"@types/send": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/express-session": {
|
||||||
|
"version": "1.18.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz",
|
||||||
|
"integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/graceful-fs": {
|
"node_modules/@types/graceful-fs": {
|
||||||
"version": "4.1.9",
|
"version": "4.1.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
|
||||||
|
|
@ -6243,6 +6255,46 @@
|
||||||
"url": "https://opencollective.com/express"
|
"url": "https://opencollective.com/express"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/express-session": {
|
||||||
|
"version": "1.18.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.2.tgz",
|
||||||
|
"integrity": "sha512-SZjssGQC7TzTs9rpPDuUrR23GNZ9+2+IkA/+IJWmvQilTr5OSliEHGF+D9scbIpdC6yGtTI0/VhaHoVes2AN/A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.7.2",
|
||||||
|
"cookie-signature": "1.0.7",
|
||||||
|
"debug": "2.6.9",
|
||||||
|
"depd": "~2.0.0",
|
||||||
|
"on-headers": "~1.1.0",
|
||||||
|
"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-signature": {
|
||||||
|
"version": "1.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
|
||||||
|
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"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==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/express/node_modules/content-disposition": {
|
"node_modules/express/node_modules/content-disposition": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz",
|
||||||
|
|
@ -6615,10 +6667,11 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/form-data": {
|
"node_modules/form-data": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
|
||||||
"integrity": "sha512-qsITQPfmvMOSAdeyZ+12I1c+CKSstAFAwu+97zrnWAbIr5u8wfsExUzCesVLC8NgHuRUqNN4Zy6UPWUTRGslcA==",
|
"integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"asynckit": "^0.4.0",
|
"asynckit": "^0.4.0",
|
||||||
"combined-stream": "^1.0.8",
|
"combined-stream": "^1.0.8",
|
||||||
|
|
@ -8759,6 +8812,15 @@
|
||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/on-headers": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/once": {
|
"node_modules/once": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
|
|
@ -9386,6 +9448,15 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/randombytes": {
|
"node_modules/randombytes": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
|
||||||
|
|
@ -10876,6 +10947,18 @@
|
||||||
"node": ">=8"
|
"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==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"random-bytes": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/uint8array-extras": {
|
"node_modules/uint8array-extras": {
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
"@prisma/client": "^6.11.1",
|
"@prisma/client": "^6.11.1",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.14.2",
|
"class-validator": "^0.14.2",
|
||||||
|
"express-session": "^1.18.2",
|
||||||
"passport": "^0.7.0",
|
"passport": "^0.7.0",
|
||||||
"passport-jwt": "^4.0.1",
|
"passport-jwt": "^4.0.1",
|
||||||
"passport-openidconnect": "^0.1.2",
|
"passport-openidconnect": "^0.1.2",
|
||||||
|
|
@ -45,6 +46,7 @@
|
||||||
"@swc/cli": "^0.6.0",
|
"@swc/cli": "^0.6.0",
|
||||||
"@swc/core": "^1.10.7",
|
"@swc/core": "^1.10.7",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
|
"@types/express-session": "^1.18.2",
|
||||||
"@types/jest": "^29.5.14",
|
"@types/jest": "^29.5.14",
|
||||||
"@types/node": "^22.10.7",
|
"@types/node": "^22.10.7",
|
||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,7 @@ export class RolesGuard implements CanActivate {
|
||||||
);
|
);
|
||||||
//for "deny-by-default" when role is wrong or unavailable
|
//for "deny-by-default" when role is wrong or unavailable
|
||||||
if (!requiredRoles || requiredRoles.length === 0) {
|
if (!requiredRoles || requiredRoles.length === 0) {
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
const request = ctx.switchToHttp().getRequest<RequestWithUser>();
|
const request = ctx.switchToHttp().getRequest<RequestWithUser>();
|
||||||
const user = request.user;
|
const user = request.user;
|
||||||
|
|
|
||||||
76
src/main.ts
76
src/main.ts
|
|
@ -2,11 +2,13 @@ import 'reflect-metadata';
|
||||||
import { ModuleRef, NestFactory, Reflector } from '@nestjs/core';
|
import { ModuleRef, NestFactory, Reflector } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { JwtAuthGuard } from './modules/authentication/guards/jwt-auth.guard';
|
// import { JwtAuthGuard } from './modules/authentication/guards/jwt-auth.guard';
|
||||||
import { RolesGuard } from './common/guards/roles.guard';
|
import { RolesGuard } from './common/guards/roles.guard';
|
||||||
import { OwnershipGuard } from './common/guards/ownership.guard';
|
import { OwnershipGuard } from './common/guards/ownership.guard';
|
||||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
import { writeFileSync } from 'fs';
|
import { writeFileSync } from 'fs';
|
||||||
|
import * as session from 'express-session';
|
||||||
|
import * as passport from 'passport';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
@ -16,41 +18,55 @@ async function bootstrap() {
|
||||||
app.useGlobalPipes(
|
app.useGlobalPipes(
|
||||||
new ValidationPipe({ whitelist: true, transform: true}));
|
new ValidationPipe({ whitelist: true, transform: true}));
|
||||||
app.useGlobalGuards(
|
app.useGlobalGuards(
|
||||||
new JwtAuthGuard(reflector), //Authentification JWT
|
// new JwtAuthGuard(reflector), //Authentification JWT
|
||||||
new RolesGuard(reflector), //deny-by-default and Role-based Access Control
|
new RolesGuard(reflector), //deny-by-default and Role-based Access Control
|
||||||
new OwnershipGuard(reflector, app.get(ModuleRef)), //Global use of OwnershipGuard, not implemented yet
|
new OwnershipGuard(reflector, app.get(ModuleRef)), //Global use of OwnershipGuard, not implemented yet
|
||||||
);
|
);
|
||||||
|
|
||||||
//swagger config
|
// Authentication and session
|
||||||
const config = new DocumentBuilder()
|
app.use(session({
|
||||||
.setTitle('Targo_Backend')
|
secret: 'This is a super secret dev secret that you cant share with anyone',
|
||||||
.setDescription('Documentation de l`API REST pour Targo (NestJS + Prisma)')
|
resave: false,
|
||||||
.setVersion('1.0')
|
saveUninitialized: false,
|
||||||
.addBearerAuth({
|
rolling: true,
|
||||||
type: 'http',
|
cookie: {
|
||||||
scheme: 'bearer',
|
maxAge: 30 * 60 * 1000,
|
||||||
bearerFormat: 'JWT',
|
httpOnly: false,
|
||||||
name: 'Authorization',
|
}
|
||||||
description: 'Invalid JWT token',
|
}))
|
||||||
in: 'header',
|
app.use(passport.initialize());
|
||||||
}, 'access-token')
|
app.use(passport.session());
|
||||||
.addTag('Users')
|
|
||||||
.addTag('Employees')
|
|
||||||
.addTag('Customers')
|
|
||||||
.addTag('Timesheets')
|
|
||||||
.addTag('Shifts')
|
|
||||||
.addTag('Leave Requests')
|
|
||||||
.addTag('Shift Codes')
|
|
||||||
.addTag('OAuth Access Tokens')
|
|
||||||
.addTag('Authorization')
|
|
||||||
.build();
|
|
||||||
|
|
||||||
//document builder for swagger docs
|
//swagger config
|
||||||
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
const config = new DocumentBuilder()
|
||||||
const document = documentFactory()
|
.setTitle('Targo_Backend')
|
||||||
SwaggerModule.setup('api/docs', app, document);
|
.setDescription('Documentation de l`API REST pour Targo (NestJS + Prisma)')
|
||||||
|
.setVersion('1.0')
|
||||||
|
.addBearerAuth({
|
||||||
|
type: 'http',
|
||||||
|
scheme: 'bearer',
|
||||||
|
bearerFormat: 'JWT',
|
||||||
|
name: 'Authorization',
|
||||||
|
description: 'Invalid JWT token',
|
||||||
|
in: 'header',
|
||||||
|
}, 'access-token')
|
||||||
|
.addTag('Users')
|
||||||
|
.addTag('Employees')
|
||||||
|
.addTag('Customers')
|
||||||
|
.addTag('Timesheets')
|
||||||
|
.addTag('Shifts')
|
||||||
|
.addTag('Leave Requests')
|
||||||
|
.addTag('Shift Codes')
|
||||||
|
.addTag('OAuth Access Tokens')
|
||||||
|
.addTag('Authorization')
|
||||||
|
.build();
|
||||||
|
|
||||||
writeFileSync('./docs/swagger/swagger-spec.json', JSON.stringify(document, null, 2));
|
//document builder for swagger docs
|
||||||
|
const documentFactory = () => SwaggerModule.createDocument(app, config);
|
||||||
|
const document = documentFactory()
|
||||||
|
SwaggerModule.setup('api/docs', app, document);
|
||||||
|
|
||||||
|
writeFileSync('./docs/swagger/swagger-spec.json', JSON.stringify(document, null, 2));
|
||||||
|
|
||||||
await app.listen(process.env.PORT ?? 3000);
|
await app.listen(process.env.PORT ?? 3000);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,22 @@ import { JwtModule } from '@nestjs/jwt';
|
||||||
import { PassportModule } from '@nestjs/passport';
|
import { PassportModule } from '@nestjs/passport';
|
||||||
import { AuthentikAuthService } from './services/authentik-auth.service';
|
import { AuthentikAuthService } from './services/authentik-auth.service';
|
||||||
import { UsersModule } from '../users-management/users.module';
|
import { UsersModule } from '../users-management/users.module';
|
||||||
|
import { AuthController } from './controllers/auth.controller';
|
||||||
|
import { AuthentikStrategy } from './strategies/authentik.strategy';
|
||||||
|
import { SessionSerializer } from './session.serializer';
|
||||||
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [ PassportModule.register({
|
||||||
PassportModule.register({ defaultStrategy: 'jwt' }),
|
session: true,
|
||||||
JwtModule.register({
|
defaultStrategy: 'openidconnect'
|
||||||
secret: process.env.JWT_SECRET || 'CHANGE_ME',
|
}), UsersModule, ],
|
||||||
signOptions: { expiresIn: '1h' },
|
providers: [
|
||||||
}),
|
AuthentikAuthService,
|
||||||
UsersModule,
|
AuthentikStrategy,
|
||||||
|
SessionSerializer,
|
||||||
],
|
],
|
||||||
providers: [ AuthentikAuthService, ],
|
|
||||||
exports: [ AuthentikAuthService ],
|
exports: [ AuthentikAuthService ],
|
||||||
|
controllers: [AuthController],
|
||||||
})
|
})
|
||||||
export class AuthenticationModule {}
|
export class AuthenticationModule {}
|
||||||
|
|
|
||||||
1
src/modules/authentication/authrequests.http
Normal file
1
src/modules/authentication/authrequests.http
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
POST http://localhost:3000/auth/login
|
||||||
17
src/modules/authentication/controllers/auth.controller.ts
Normal file
17
src/modules/authentication/controllers/auth.controller.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { Controller, Get, Req, Res, UseGuards } from '@nestjs/common';
|
||||||
|
import { OIDCLoginGuard } from '../guards/authentik-auth.guard';
|
||||||
|
import { Request, Response } from 'express';
|
||||||
|
|
||||||
|
@Controller('auth')
|
||||||
|
export class AuthController {
|
||||||
|
|
||||||
|
@UseGuards(OIDCLoginGuard)
|
||||||
|
@Get('/login')
|
||||||
|
login() {}
|
||||||
|
|
||||||
|
@Get('/callback')
|
||||||
|
@UseGuards(OIDCLoginGuard)
|
||||||
|
loginCallback(@Req() req: Request, @Res() res: Response) {
|
||||||
|
res.redirect('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
import { Controller, Get, Post, Req, UseGuards } from '@nestjs/common';
|
|
||||||
import { Request } from 'express';
|
|
||||||
|
|
||||||
@Controller('auth')
|
|
||||||
export class AuthController {
|
|
||||||
@Post('/login')
|
|
||||||
login() {
|
|
||||||
// Passport handles redirection to Authentik
|
|
||||||
}
|
|
||||||
|
|
||||||
@Get('callback')
|
|
||||||
// @UseGuards(AuthGuard('openidconnect'))
|
|
||||||
callback(@Req() req: Request) {
|
|
||||||
console.log('✅ Auth complete, here is req.user:');
|
|
||||||
console.dir(req.user, { depth: null });
|
|
||||||
|
|
||||||
return req.user;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
src/modules/authentication/guards/authentik-auth.guard.ts
Normal file
12
src/modules/authentication/guards/authentik-auth.guard.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
import { ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class OIDCLoginGuard extends AuthGuard('openidconnect') {
|
||||||
|
async canActivate(context: ExecutionContext) {
|
||||||
|
const result = (await super.canActivate(context)) as boolean;
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
await super.logIn(request);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
18
src/modules/authentication/session.serializer.ts
Normal file
18
src/modules/authentication/session.serializer.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import { PassportSerializer } from '@nestjs/passport';
|
||||||
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SessionSerializer extends PassportSerializer {
|
||||||
|
serializeUser(user: any, done: (err: any, user: any) => void): any {
|
||||||
|
if (!user){
|
||||||
|
done(new UnauthorizedException('Serialize user error'), user);
|
||||||
|
}
|
||||||
|
done(null, user);
|
||||||
|
}
|
||||||
|
deserializeUser(payload: any, done: (err: any, payload: string) => void): any {
|
||||||
|
if (!payload){
|
||||||
|
done(new UnauthorizedException('Deserialize user error'), payload);
|
||||||
|
}
|
||||||
|
done(null, payload);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
import { Strategy as OIDCStrategy } from 'passport-openidconnect';
|
import { Strategy as OIDCStrategy, Profile, VerifyCallback } from 'passport-openidconnect';
|
||||||
import { PassportStrategy } from '@nestjs/passport';
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { AuthentikAuthService } from '../services/authentik-auth.service';
|
import { AuthentikAuthService } from '../services/authentik-auth.service';
|
||||||
|
|
||||||
export interface AuthentikPayload {
|
export interface AuthentikPayload {
|
||||||
|
|
@ -23,26 +23,33 @@ export interface AuthentikPayload {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthentikStrategy extends PassportStrategy(OIDCStrategy) {
|
export class AuthentikStrategy extends PassportStrategy(OIDCStrategy, 'openidconnect', 8) {
|
||||||
constructor(private authentikAuthService: AuthentikAuthService) {
|
constructor(private authentikAuthService: AuthentikAuthService) {
|
||||||
super({
|
super({
|
||||||
issuer: process.env.AUTHENTIK_ISSUER || "ISSUER_MISSING",
|
issuer: process.env.AUTHENTIK_ISSUER || "ISSUER_MISSING",
|
||||||
clientID: process.env.AUTHENTIK_CLIENT_ID || 'CLIENT_ID_MISSING',
|
clientID: process.env.AUTHENTIK_CLIENT_ID || 'CLIENT_ID_MISSING',
|
||||||
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET || 'CLIENT_SECRET_MISSING',
|
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET || 'CLIENT_SECRET_MISSING',
|
||||||
callbackURL: process.env.AUTHENTIK_CALLBACK_URL || 'CALLBACK_URL_MISSING',
|
callbackURL: process.env.AUTHENTIK_CALLBACK_URL || 'CALLBACK_URL_MISSING',
|
||||||
authorizationURL: `${process.env.AUTHENTIK_ISSUER}/authorize/`,
|
authorizationURL: process.env.AUTHENTIK_AUTH_URL || 'AUTH_URL_MISSING',
|
||||||
tokenURL: `${process.env.AUTHENTIK_ISSUER}/token/`,
|
tokenURL: process.env.AUTHENTIK_TOKEN_URL || 'TOKEN_URL_MISSING',
|
||||||
userInfoURL: `${process.env.AUTHENTIK_ISSUER}/userinfo/`,
|
userInfoURL: process.env.AUTHENTIK_USERINFO_URL || 'USERINFO_URL_MISSING',
|
||||||
scope: ['openid', 'email', 'profile'],
|
scope: ['openid', 'email', 'profile', 'offline_access'],
|
||||||
});
|
},);
|
||||||
}
|
}
|
||||||
|
|
||||||
async validate(payload: AuthentikPayload): Promise<any> {
|
async validate(
|
||||||
// if ( !payload.email_verified ) { throw new UnauthorizedException(); }
|
issuer: string,
|
||||||
|
profile: Profile,
|
||||||
|
_context: any,
|
||||||
|
idToken: string,
|
||||||
|
accessToken: string,
|
||||||
|
refreshToken: string,
|
||||||
|
params: any,
|
||||||
|
cb: VerifyCallback,
|
||||||
|
): Promise<any> {
|
||||||
|
|
||||||
const user = await this.authentikAuthService.validateUser(payload.email);
|
// saving all info from validate() into NestJS session under 'user'
|
||||||
if ( !user ) { throw new UnauthorizedException(); }
|
/* TODO: Will need to add authorization logic with Prisma here later */
|
||||||
|
return cb(null, { issuer, ...profile, idToken, accessToken, refreshToken, ...params });
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user