import { Strategy as OIDCStrategy, Profile, VerifyCallback } from 'passport-openidconnect'; import { PassportStrategy } from '@nestjs/passport'; import { Injectable } from '@nestjs/common'; import { AuthentikAuthService } from '../services/authentik-auth.service'; import { ValidationError } from 'class-validator'; export interface AuthentikPayload { iss: string; // Issuer sub: string; // Subject (user ID) aud: string; // Audience (client ID) exp: number; // Expiration time (Unix) iat: number; // Issued at time (Unix) auth_time: number; // Time of authentication (Unix) acr?: string; // Auth Context Class Reference amr?: string[]; // Auth Method References (e.g., ['pwd']) email: string; email_verified: boolean; name?: string; given_name?: string; preferred_username?: string; nickname?: string; groups?: string[]; } @Injectable() export class AuthentikStrategy extends PassportStrategy(OIDCStrategy, 'openidconnect', 8) { constructor(private authentikAuthService: AuthentikAuthService) { 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_AUTH_URL || 'AUTH_URL_MISSING', tokenURL: process.env.AUTHENTIK_TOKEN_URL || 'TOKEN_URL_MISSING', userInfoURL: process.env.AUTHENTIK_USERINFO_URL || 'USERINFO_URL_MISSING', scope: ['openid', 'email', 'profile', 'offline_access'], },); } async validate( _issuer: string, profile: Profile, _context: any, _idToken: string, _accessToken: string, _refreshToken: string, _params: any, cb: VerifyCallback, ): Promise { try { const email = profile.emails?.[0]?.value; if (!email) return cb(new Error('Missing email in OIDC profile'), false); const user = await this.authentikAuthService.validateUser(email); if (!user) return cb(new Error('User not found'), false); return cb(null, user); } catch (err) { return cb(err, false); } } }