build(authentication): Add Authentik strategy, import new passport oidc module, add swagger desc for roles guard, remove index for auth modules

This commit is contained in:
Nicolas Drolet 2025-07-17 17:06:43 -04:00
parent d70bdab1e9
commit 58287dcac3
6 changed files with 117 additions and 0 deletions

48
package-lock.json generated
View File

@ -21,6 +21,7 @@
"class-validator": "^0.14.2", "class-validator": "^0.14.2",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-openidconnect": "^0.1.2",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1" "rxjs": "^7.8.1"
}, },
@ -36,6 +37,7 @@
"@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",
"@types/passport-openidconnect": "^0.1.3",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",
@ -3354,6 +3356,16 @@
"undici-types": "~6.21.0" "undici-types": "~6.21.0"
} }
}, },
"node_modules/@types/oauth": {
"version": "0.9.6",
"resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz",
"integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/passport": { "node_modules/@types/passport": {
"version": "1.0.17", "version": "1.0.17",
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz", "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.17.tgz",
@ -3373,6 +3385,19 @@
"@types/passport-strategy": "*" "@types/passport-strategy": "*"
} }
}, },
"node_modules/@types/passport-openidconnect": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@types/passport-openidconnect/-/passport-openidconnect-0.1.3.tgz",
"integrity": "sha512-k1Ni7bG/9OZNo2Qpjg2W6GajL+pww6ZPaNWMXfpteCX4dXf4QgaZLt2hjR5IiPrqwBT9+W8KjCTJ/uhGIoBx/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/express": "*",
"@types/oauth": "*",
"@types/passport": "*",
"@types/passport-strategy": "*"
}
},
"node_modules/@types/passport-strategy": { "node_modules/@types/passport-strategy": {
"version": "0.2.38", "version": "0.2.38",
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz", "resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.38.tgz",
@ -8689,6 +8714,12 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/oauth": {
"version": "0.10.2",
"resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz",
"integrity": "sha512-JtFnB+8nxDEXgNyniwz573xxbKSOu3R8D40xQKqcjwJ2CDkYqUDI53o6IuzDJBx60Z8VKCm271+t8iFjakrl8Q==",
"license": "MIT"
},
"node_modules/object-assign": { "node_modules/object-assign": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -8930,6 +8961,23 @@
"passport-strategy": "^1.0.0" "passport-strategy": "^1.0.0"
} }
}, },
"node_modules/passport-openidconnect": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/passport-openidconnect/-/passport-openidconnect-0.1.2.tgz",
"integrity": "sha512-JX3rTyW+KFZ/E9OF/IpXJPbyLO9vGzcmXB5FgSP2jfL3LGKJPdV7zUE8rWeKeeI/iueQggOeFa3onrCmhxXZTg==",
"license": "MIT",
"dependencies": {
"oauth": "0.10.x",
"passport-strategy": "1.x.x"
},
"engines": {
"node": ">= 0.6.0"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jaredhanson"
}
},
"node_modules/passport-strategy": { "node_modules/passport-strategy": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",

View File

@ -32,6 +32,7 @@
"class-validator": "^0.14.2", "class-validator": "^0.14.2",
"passport": "^0.7.0", "passport": "^0.7.0",
"passport-jwt": "^4.0.1", "passport-jwt": "^4.0.1",
"passport-openidconnect": "^0.1.2",
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"rxjs": "^7.8.1" "rxjs": "^7.8.1"
}, },
@ -47,6 +48,7 @@
"@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",
"@types/passport-openidconnect": "^0.1.3",
"@types/supertest": "^6.0.2", "@types/supertest": "^6.0.2",
"eslint": "^9.18.0", "eslint": "^9.18.0",
"eslint-config-prettier": "^10.0.1", "eslint-config-prettier": "^10.0.1",

View File

@ -9,6 +9,8 @@ import { ROLES_KEY } from '../decorators/roles.decorators';
import { Roles } from '.prisma/client'; import { Roles } from '.prisma/client';
import { JwtPayload } from 'src/modules/authentication/strategies/jwt.strategy'; import { JwtPayload } from 'src/modules/authentication/strategies/jwt.strategy';
interface RequestWithUser extends Request { interface RequestWithUser extends Request {
user: JwtPayload; user: JwtPayload;
} }
@ -17,6 +19,23 @@ interface RequestWithUser extends Request {
export class RolesGuard implements CanActivate { export class RolesGuard implements CanActivate {
constructor(private reflector: Reflector) {} constructor(private reflector: Reflector) {}
/**
* @swagger
* @function canActivate
* @description
* Authorization guard that checks whether the current user has one of the required roles
* to access a specific route handler. It uses metadata defined by the `@Roles()` decorator
* and verifies the user's role accordingly.
*
* If no roles are specified for the route, access is granted by default.
* If the user is not authenticated or does not have a required role, access is denied.
*
* @param {ExecutionContext} ctx - The current execution context, which provides access
* to route metadata and the HTTP request.
*
* @returns {boolean} - Returns `true` if access is allowed, otherwise throws a `ForbiddenException`
* or returns `false` if the user is not authenticated.
*/
canActivate(ctx: ExecutionContext): boolean { canActivate(ctx: ExecutionContext): boolean {
const requiredRoles = this.reflector.get<Roles[]>( const requiredRoles = this.reflector.get<Roles[]>(
ROLES_KEY, ROLES_KEY,

View File

@ -0,0 +1,20 @@
import { Injectable } from '@nestjs/common';
import { UUID } from 'crypto';
import { UsersService } from 'src/modules/users-management/services/users.service';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private usersService: UsersService,
private jwtService: JwtService
) {}
async validateUser(user_id: UUID): Promise<any> {
const user = await this.usersService.findOne(user_id);
if (user) {
return user;
}
return null;
}
}

View File

@ -0,0 +1,28 @@
import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-openidconnect';
export interface AuthentikPayload {
//TODO: check Authentik payload contents
}
@Injectable()
export class AuthentikStrategy extends PassportStrategy(Strategy) {
constructor() {
super({
issuer: process.env.AUTHENTIK_ISSUER || "ISSUER MISSING",
clientID: process.env.AUTHENTIK_CLIENT_ID || "CLIENT ID MISSING",
clientSecret: process.env.AUTHENTIK_CLIENT_SECRET || "CLIENT SECRET MISSING",
callbackURL: process.env.AUTHENTIK_CALLBACK_URL || "CALLBACK URL MISSING",
authorizationURL: `${process.env.AUTHENTIK_ISSUER}/authorize/`,
tokenURL: `${process.env.AUTHENTIK_ISSUER}/token/`,
userInfoURL: `${process.env.AUTHENTIK_ISSUER}/userinfo/`,
scope: [],
})
}
async validate(): Promise<any> {
// Check what kind of payload we're actually handling here
return null;
}
}