@wristband/nestjs-auth
Version:
SDK for integrating your NestJS application with Wristband. Handles user authentication, session management, and token management.
168 lines (167 loc) • 7.75 kB
JavaScript
;
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.createWristbandAuthGuard = createWristbandAuthGuard;
const common_1 = require("@nestjs/common");
const common_2 = require("@nestjs/common");
const config_1 = require("@nestjs/config");
const auth_service_1 = require("../auth/auth.service");
const constants_1 = require("../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,
* }
* }
* }));
* ```
*/
function createWristbandAuthGuard(configKey = 'wristbandAuthGuard', authToken = constants_1.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 common_1.UnauthorizedException(err.message || 'Unauthorized'));
break;
case 403:
reject(new common_1.ForbiddenException(err.message || 'Forbidden'));
break;
default:
reject(new common_1.InternalServerErrorException(err.message || 'Internal server error'));
}
}
else {
resolve(true);
}
});
});
}
};
WristbandAuthGuardMixin = __decorate([
(0, common_1.Injectable)(),
__param(0, (0, common_2.Inject)(authToken)),
__metadata("design:paramtypes", [auth_service_1.WristbandExpressAuthService,
config_1.ConfigService])
], WristbandAuthGuardMixin);
return (0, common_1.mixin)(WristbandAuthGuardMixin);
}