"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.AuthService = void 0; const common_1 = require("@nestjs/common"); const config_1 = require("@nestjs/config"); const jwt_1 = require("@nestjs/jwt"); const sync_identity_handler_1 = require("../identity/application/handlers/user/sync-identity.handler"); const user_interface_1 = require("../identity/domain/repositories/user.interface"); const prisma_service_1 = require("../../shared/prisma.service"); const redis_pkce_store_1 = require("./pkce/redis-pkce.store"); const inmemory_refresh_store_1 = require("./refresh/inmemory-refresh.store"); const openidClient = require('openid-client'); let AuthService = class AuthService { config; jwtService; prisma; userRepository; syncIdentityHandler; pkceStore; refreshStore; constructor(config, jwtService, prisma, userRepository, syncIdentityHandler, pkceStore, refreshStore) { this.config = config; this.jwtService = jwtService; this.prisma = prisma; this.userRepository = userRepository; this.syncIdentityHandler = syncIdentityHandler; this.pkceStore = pkceStore; this.refreshStore = refreshStore; } issuer = null; client = null; async getIssuer() { if (this.issuer) return this.issuer; const issuerUrl = this.config.get('AUTHENTIK_ISSUER'); if (!issuerUrl) throw new Error('AUTHENTIK_ISSUER not configured'); this.issuer = await openidClient.Issuer.discover(issuerUrl); return this.issuer; } async getClient() { if (this.client) return this.client; const issuer = await this.getIssuer(); const clientId = this.config.get('AUTHENTIK_CLIENT_ID'); const clientSecret = this.config.get('AUTHENTIK_CLIENT_SECRET'); if (!clientId) throw new Error('AUTHENTIK_CLIENT_ID not configured'); this.client = new issuer.Client({ client_id: clientId, client_secret: clientSecret }); return this.client; } async createAuthorizationRedirect(returnTo) { const client = await this.getClient(); const redirectUri = this.config.get('AUTH_CALLBACK_URL') || `${this.config.get('APP_URL') || ''}/auth/callback`; const state = require('crypto').randomUUID(); const code_verifier = openidClient.generators.codeVerifier(); const code_challenge = await openidClient.generators.codeChallenge(code_verifier); const nonce = openidClient.generators.nonce(); await this.pkceStore.save(state, { code_verifier, nonce, returnTo }, 300); const url = client.authorizationUrl({ redirect_uri: redirectUri, scope: this.config.get('AUTHENTIK_DEFAULT_SCOPE') || 'openid email profile', response_type: 'code', code_challenge, code_challenge_method: 'S256', state, nonce, }); return url; } async handleCallback(code, state) { const client = await this.getClient(); const pkce = await this.pkceStore.get(state); if (!pkce) throw new common_1.UnauthorizedException('Invalid or expired state'); // remove one-time state await this.pkceStore.remove(state); const redirectUri = this.config.get('AUTH_CALLBACK_URL') || `${this.config.get('APP_URL') || ''}/auth/callback`; // exchange code const tokenSet = await client.callback(redirectUri, { code, state }, { code_verifier: pkce.code_verifier, nonce: pkce.nonce }); // verify id_token and get claims const claims = tokenSet.claims(); // fetch userinfo if available let userInfo = null; try { if (tokenSet.access_token && client.userinfo) { userInfo = await client.userinfo(tokenSet.access_token); } } catch (e) { // ignore } const identity = { sub: userInfo?.sub || claims.sub || null, preferred_username: userInfo?.preferred_username || userInfo?.username || userInfo?.email || claims.preferred_username || claims.email, email: userInfo?.email || claims.email, raw: { tokenSet, userInfo, claims }, }; // Sync identity to local user (create if needed) const domainUser = await this.syncIdentityHandler.execute(identity); // ensure active/not deleted if (!domainUser.isActive) throw new common_1.UnauthorizedException('User is not active'); if (domainUser.deletedAt) throw new common_1.UnauthorizedException('User is deleted'); // create internal JWT const jwtPayload = { sub: domainUser.id, preferred_username: domainUser.username, email: domainUser.email, }; const expiresIn = Number(this.config.get('RAYLAB_JWT_EXPIRES_IN') || 3600); const access = this.jwtService.sign(jwtPayload, { expiresIn }); // create internal refresh token const refreshToken = require('crypto').randomUUID(); const refreshTtl = Number(this.config.get('RAYLAB_REFRESH_EXPIRES_IN') || 30 * 24 * 3600); // default 30 days await this.refreshStore.set(refreshToken, { userId: domainUser.id }, refreshTtl); return { accessToken: access, refreshToken, expiresIn, refreshTtl, user: { id: domainUser.id, username: domainUser.username, email: domainUser.email, roles: domainUser.roles || [], }, returnTo: pkce.returnTo, }; } async refresh(refreshToken) { if (!refreshToken) throw new common_1.UnauthorizedException('Missing refresh token'); const data = await this.refreshStore.get(refreshToken); if (!data) throw new common_1.UnauthorizedException('Invalid refresh token'); const userId = data.userId; // load user const domainUser = await this.userRepository.getById(userId); if (!domainUser) throw new common_1.UnauthorizedException('User not found'); if (!domainUser.isActive) throw new common_1.UnauthorizedException('User is not active'); // rotate refresh token await this.refreshStore.del(refreshToken); const newRefresh = require('crypto').randomUUID(); const refreshTtl = Number(this.config.get('RAYLAB_REFRESH_EXPIRES_IN') || 30 * 24 * 3600); await this.refreshStore.set(newRefresh, { userId }, refreshTtl); const expiresIn = Number(this.config.get('RAYLAB_JWT_EXPIRES_IN') || 3600); const access = this.jwtService.sign({ sub: domainUser.id, preferred_username: domainUser.username, email: domainUser.email }, { expiresIn }); return { accessToken: access, refreshToken: newRefresh, expiresIn, refreshTtl }; } async logout(refreshToken) { if (refreshToken) { await this.refreshStore.del(refreshToken); } const issuer = await this.getIssuer(); const endSession = issuer.metadata.end_session_endpoint; const postLogout = this.config.get('APP_URL') || '/'; if (endSession) { // Redirect to identity provider logout const url = new URL(endSession); if (postLogout) url.searchParams.set('post_logout_redirect_uri', postLogout); return url.toString(); } return postLogout; } async me(token) { if (!token) throw new common_1.UnauthorizedException('Missing token'); try { const payload = this.jwtService.verify(token); const user = await this.userRepository.getById(payload.sub); if (!user) throw new common_1.UnauthorizedException('User not found'); return { id: user.id, username: user.username, email: user.email, roles: user.roles || [] }; } catch (e) { throw new common_1.UnauthorizedException('Invalid token'); } } }; exports.AuthService = AuthService; exports.AuthService = AuthService = __decorate([ (0, common_1.Injectable)(), __param(3, (0, common_1.Inject)(user_interface_1.IUser)), __metadata("design:paramtypes", [config_1.ConfigService, jwt_1.JwtService, prisma_service_1.PrismaService, user_interface_1.IUser, sync_identity_handler_1.SyncIdentityHandler, redis_pkce_store_1.RedisPkceStore, inmemory_refresh_store_1.InMemoryRefreshStore]) ], AuthService); //# sourceMappingURL=auth.service.js.map