UNPKG

@wristband/nestjs-auth

Version:

SDK for integrating your NestJS application with Wristband. Handles user authentication, session management, and token management.

165 lines (164 loc) 7.62 kB
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); } }; import { Injectable, UnauthorizedException, ForbiddenException, InternalServerErrorException, mixin, } from '@nestjs/common'; import { Inject } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { WristbandExpressAuthService } from '../auth/auth.service'; import { DEFAULT_AUTH_TOKEN } from '../constants'; /** * Factory function to create a Wristband authentication guard. * * Creates middleware that validates authentication using configurable strategies (SESSION, JWT, or both). * Supports multi-strategy authentication with automatic fallback between strategies. * * The factory exists to create distinct guard identities for cases where multiple guards with * different configurations are needed in the same application. * * Strategy behavior: * - SESSION: Validates session authentication, optionally checks CSRF, and refreshes expired tokens * - JWT: Validates JWT bearer tokens from Authorization header * - Multi-strategy: Tries strategies in configured order, falls back to next on failure * * NOTE: Token refresh only occurs when both `refreshToken` and `expiresAt` are present in the session. * * @param configKey - The ConfigModule key to read guard configuration from (default: 'wristbandAuthGuard') * @param authToken - The DI token for the WristbandExpressAuthService (default: DEFAULT_AUTH_TOKEN) * @returns A guard class that can be used with @UseGuards() * * @throws {UnauthorizedException} If all configured strategies fail authentication or token refresh fails * @throws {ForbiddenException} If CSRF token validation fails (SESSION strategy only) * @throws {InternalServerErrorException} If an unexpected error occurs during authentication * * @example * ```typescript * // Default guard (reads from 'wristbandAuthGuard' config) * export const WristbandAuthGuard = createWristbandAuthGuard(); * * @Controller('api') * export class MyController { * @Get('protected') * @UseGuards(WristbandAuthGuard) * protectedRoute() { * return { message: 'Authenticated' }; * } * } * ``` * * @example * ```typescript * // Custom guard with different config and service instance * export const AdminAuthGuard = createWristbandAuthGuard('adminAuthGuard', 'ADMIN_AUTH_TOKEN'); * * @Controller('admin') * export class AdminController { * @Get('dashboard') * @UseGuards(AdminAuthGuard) * adminDashboard() { * return { message: 'Admin access granted' }; * } * } * ``` * * @example * ```typescript * // Guard configuration in ConfigModule * import { registerAs } from '@nestjs/config'; * import type { AuthGuardConfig } from '@wristband/nestjs-auth'; * * export const authGuardConfig = registerAs('wristbandAuthGuard', (): AuthGuardConfig => ({ * authStrategies: ['SESSION', 'JWT'], * sessionConfig: { * sessionOptions: { * secrets: process.env.SESSION_SECRET, * enableCsrfProtection: true, * } * } * })); * ``` */ export function createWristbandAuthGuard(configKey = 'wristbandAuthGuard', authToken = DEFAULT_AUTH_TOKEN) { let WristbandAuthGuardMixin = class WristbandAuthGuardMixin { constructor(authService, configService) { this.authService = authService; this.configService = configService; // Validate auth service exists if (!this.authService) { throw new Error(`WristbandExpressAuthService not found. Ensure WristbandExpressAuthModule is imported in your module.`); } // Read guard configuration from ConfigService const guardConfig = this.configService.get(configKey); if (!guardConfig) { throw new Error(`Auth guard configuration not found for key '${configKey}'. ` + `Ensure config is registered via ConfigModule.forRoot({ load: [...] })`); } // Validate configuration based on strategies this.validateGuardConfig(guardConfig); // Create auth middleware with the guard config this.authMiddleware = this.authService.wristbandAuth.createAuthMiddleware(guardConfig); } /** * Validates guard configuration based on selected auth strategies. */ validateGuardConfig(config) { const { authStrategies, sessionConfig } = config; if (!authStrategies || authStrategies.length === 0) { throw new Error('Auth guard requires at least one auth strategy'); } // If SESSION strategy is used, validate session configuration if (authStrategies.includes('SESSION')) { if (!sessionConfig?.sessionOptions) { throw new Error('SESSION auth strategy requires sessionConfig.sessionOptions'); } const { secrets } = sessionConfig.sessionOptions; if (!secrets || (Array.isArray(secrets) && secrets.length === 0)) { throw new Error('SESSION auth strategy requires sessionConfig.sessionOptions.secrets'); } } } /** * Guard implementation that validates authentication using configured strategies. */ async canActivate(context) { const request = context.switchToHttp().getRequest(); const response = context.switchToHttp().getResponse(); return new Promise((resolve, reject) => { this.authMiddleware(request, response, (err) => { if (err) { // Map Express middleware errors to NestJS exceptions const statusCode = err.status || err.statusCode || 500; switch (statusCode) { case 401: reject(new UnauthorizedException(err.message || 'Unauthorized')); break; case 403: reject(new ForbiddenException(err.message || 'Forbidden')); break; default: reject(new InternalServerErrorException(err.message || 'Internal server error')); } } else { resolve(true); } }); }); } }; WristbandAuthGuardMixin = __decorate([ Injectable(), __param(0, Inject(authToken)), __metadata("design:paramtypes", [WristbandExpressAuthService, ConfigService]) ], WristbandAuthGuardMixin); return mixin(WristbandAuthGuardMixin); }