@lenan-soft/auth 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/nestjs/lenan-auth.module.ts","../../src/nestjs/controllers/auth.controller.ts","../../src/nestjs/decorators/index.ts","../../src/nestjs/dto/index.ts","../../src/nestjs/guards/jwt-auth.guard.ts","../../src/nestjs/guards/refresh-token.guard.ts","../../src/nestjs/interfaces/index.ts","../../src/nestjs/services/auth.service.ts","../../src/nestjs/services/jwt-token.service.ts","../../src/nestjs/services/password.service.ts","../../src/nestjs/strategies/jwt.strategy.ts","../../src/nestjs/strategies/refresh-token.strategy.ts"],"sourcesContent":["import { DynamicModule, Module, Provider, Type } from \"@nestjs/common\";\nimport { JwtModule } from \"@nestjs/jwt\";\nimport { PassportModule } from \"@nestjs/passport\";\nimport { AuthController } from \"./controllers\";\nimport {\n LENAN_AUTH_OPTIONS,\n LENAN_USER_SERVICE,\n type AuthModuleAsyncOptions,\n type AuthModuleOptions,\n type AuthOptionsFactory,\n type UserServiceInterface,\n} from \"./interfaces\";\nimport { AuthService, JwtTokenService, PasswordService } from \"./services\";\nimport { JwtStrategy, RefreshTokenStrategy } from \"./strategies\";\n\n/**\n * Lenan Auth Module for NestJS\n *\n * A flexible authentication module supporting JWT access/refresh tokens.\n * Requires consumers to implement UserServiceInterface.\n *\n * @example\n * ```typescript\n * // Sync registration\n * @Module({\n * imports: [\n * LenanAuthModule.register({\n * jwtSecret: 'your-secret',\n * jwtAccessExpiry: '15m',\n * jwtRefreshExpiry: '7d',\n * }, UserService),\n * ],\n * })\n * export class AppModule {}\n *\n * // Async registration with ConfigService\n * @Module({\n * imports: [\n * LenanAuthModule.registerAsync({\n * imports: [ConfigModule],\n * useFactory: (config: ConfigService) => ({\n * jwtSecret: config.get('JWT_SECRET'),\n * jwtAccessExpiry: config.get('JWT_ACCESS_EXPIRY', '15m'),\n * jwtRefreshExpiry: config.get('JWT_REFRESH_EXPIRY', '7d'),\n * }),\n * inject: [ConfigService],\n * }, UserService),\n * ],\n * })\n * export class AppModule {}\n * ```\n */\n@Module({})\nexport class LenanAuthModule {\n /**\n * Register the auth module with synchronous configuration\n *\n * @param options - Auth module configuration options\n * @param userService - Your UserService class implementing UserServiceInterface\n */\n static register(\n options: AuthModuleOptions,\n userService: Type<UserServiceInterface>,\n ): DynamicModule {\n const providers = this.createProviders(options, userService);\n const controllers = options.useController !== false ? [AuthController] : [];\n\n return {\n module: LenanAuthModule,\n imports: [\n PassportModule.register({ defaultStrategy: \"lenan-jwt\" }),\n JwtModule.register({\n secret: options.jwtSecret,\n signOptions: { expiresIn: (options.jwtAccessExpiry ?? \"15m\") as any },\n }),\n ],\n providers,\n controllers,\n exports: [\n AuthService,\n PasswordService,\n JwtTokenService,\n JwtStrategy,\n LENAN_USER_SERVICE,\n ],\n };\n }\n\n /**\n * Register the auth module with async configuration\n *\n * @param asyncOptions - Async configuration options\n * @param userService - Your UserService class implementing UserServiceInterface\n */\n static registerAsync(\n asyncOptions: AuthModuleAsyncOptions,\n userService: Type<UserServiceInterface>,\n ): DynamicModule {\n const providers = this.createAsyncProviders(asyncOptions, userService);\n\n return {\n module: LenanAuthModule,\n imports: [\n ...(asyncOptions.imports ?? []),\n PassportModule.register({ defaultStrategy: \"lenan-jwt\" }),\n JwtModule.registerAsync({\n imports: asyncOptions.imports,\n useFactory: async (...args: any[]) => {\n let options: AuthModuleOptions;\n\n if (asyncOptions.useFactory) {\n options = await asyncOptions.useFactory(...args);\n } else {\n throw new Error(\"useFactory is required for async registration\");\n }\n\n return {\n secret: options.jwtSecret,\n signOptions: {\n expiresIn: (options.jwtAccessExpiry ?? \"15m\") as any,\n },\n };\n },\n inject: asyncOptions.inject ?? [],\n }),\n ],\n providers,\n controllers: [AuthController],\n exports: [\n AuthService,\n PasswordService,\n JwtTokenService,\n JwtStrategy,\n LENAN_USER_SERVICE,\n ],\n };\n }\n\n private static createProviders(\n options: AuthModuleOptions,\n userService: Type<UserServiceInterface>,\n ): Provider[] {\n return [\n {\n provide: LENAN_AUTH_OPTIONS,\n useValue: options,\n },\n {\n provide: LENAN_USER_SERVICE,\n useClass: userService,\n },\n AuthService,\n PasswordService,\n JwtTokenService,\n JwtStrategy,\n RefreshTokenStrategy,\n ];\n }\n\n private static createAsyncProviders(\n asyncOptions: AuthModuleAsyncOptions,\n userService: Type<UserServiceInterface>,\n ): Provider[] {\n const asyncOptionsProvider = this.createAsyncOptionsProvider(asyncOptions);\n\n return [\n asyncOptionsProvider,\n {\n provide: LENAN_USER_SERVICE,\n useClass: userService,\n },\n AuthService,\n PasswordService,\n JwtTokenService,\n JwtStrategy,\n RefreshTokenStrategy,\n ];\n }\n\n private static createAsyncOptionsProvider(\n asyncOptions: AuthModuleAsyncOptions,\n ): Provider {\n if (asyncOptions.useFactory) {\n return {\n provide: LENAN_AUTH_OPTIONS,\n useFactory: asyncOptions.useFactory,\n inject: asyncOptions.inject ?? [],\n };\n }\n\n if (asyncOptions.useClass) {\n return {\n provide: LENAN_AUTH_OPTIONS,\n useFactory: async (optionsFactory: AuthOptionsFactory) => {\n return optionsFactory.createAuthOptions();\n },\n inject: [asyncOptions.useClass],\n };\n }\n\n if (asyncOptions.useExisting) {\n return {\n provide: LENAN_AUTH_OPTIONS,\n useFactory: async (optionsFactory: AuthOptionsFactory) => {\n return optionsFactory.createAuthOptions();\n },\n inject: [asyncOptions.useExisting],\n };\n }\n\n throw new Error(\n \"Invalid async options: must provide useFactory, useClass, or useExisting\",\n );\n }\n}\n","import {\n Body,\n Controller,\n Get,\n HttpCode,\n HttpStatus,\n Post,\n UseGuards,\n} from \"@nestjs/common\";\nimport type { BaseUser } from \"../../shared\";\nimport { CurrentUser, Public } from \"../decorators\";\nimport {\n LoginDto,\n MessageResponseDto,\n RegisterDto,\n TokensResponseDto,\n} from \"../dto\";\nimport { JwtAuthGuard, RefreshTokenGuard } from \"../guards\";\nimport { AuthService } from \"../services/auth.service\";\n\n/**\n * Authentication controller providing standard auth endpoints\n * This controller is optional - consumers can disable it and implement their own\n */\n@Controller(\"auth\")\nexport class AuthController {\n constructor(private readonly authService: AuthService) {}\n\n /**\n * Register a new user\n * POST /auth/register\n */\n @Public()\n @Post(\"register\")\n @HttpCode(HttpStatus.CREATED)\n async register(@Body() dto: RegisterDto): Promise<TokensResponseDto> {\n const tokens = await this.authService.register(dto);\n return new TokensResponseDto(tokens.accessToken, tokens.refreshToken);\n }\n\n /**\n * Login with email and password\n * POST /auth/login\n */\n @Public()\n @Post(\"login\")\n @HttpCode(HttpStatus.OK)\n async login(@Body() dto: LoginDto): Promise<TokensResponseDto> {\n const tokens = await this.authService.login(dto);\n return new TokensResponseDto(tokens.accessToken, tokens.refreshToken);\n }\n\n /**\n * Refresh access token using refresh token\n * POST /auth/refresh\n */\n @Public()\n @UseGuards(RefreshTokenGuard)\n @Post(\"refresh\")\n @HttpCode(HttpStatus.OK)\n async refresh(\n @CurrentUser() user: BaseUser & { refreshToken: string },\n ): Promise<TokensResponseDto> {\n const tokens = await this.authService.refreshTokens(\n user.id,\n user.refreshToken,\n );\n return new TokensResponseDto(tokens.accessToken, tokens.refreshToken);\n }\n\n /**\n * Logout (invalidate refresh token)\n * POST /auth/logout\n */\n @UseGuards(JwtAuthGuard)\n @Post(\"logout\")\n @HttpCode(HttpStatus.OK)\n async logout(@CurrentUser(\"id\") userId: string): Promise<MessageResponseDto> {\n await this.authService.logout(userId);\n return new MessageResponseDto(\"Logged out successfully\");\n }\n\n /**\n * Get current authenticated user\n * GET /auth/me\n */\n @UseGuards(JwtAuthGuard)\n @Get(\"me\")\n async me(@CurrentUser() user: BaseUser) {\n return user;\n }\n}\n","import {\n createParamDecorator,\n ExecutionContext,\n SetMetadata,\n} from \"@nestjs/common\";\n\n/**\n * Metadata key for public routes\n */\nexport const IS_PUBLIC_KEY = \"isPublic\";\n\n/**\n * Mark a route as public (no authentication required)\n * Use this decorator on routes that should be accessible without a valid JWT\n *\n * @example\n * ```typescript\n * @Public()\n * @Get('health')\n * healthCheck() {\n * return { status: 'ok' };\n * }\n * ```\n */\nexport const Public = () => SetMetadata(IS_PUBLIC_KEY, true);\n\n/**\n * Extract the current authenticated user from the request\n * Can optionally extract a specific property from the user object\n *\n * @example\n * ```typescript\n * // Get the entire user object\n * @Get('profile')\n * getProfile(@CurrentUser() user: User) {\n * return user;\n * }\n *\n * // Get a specific property\n * @Get('my-id')\n * getMyId(@CurrentUser('id') userId: string) {\n * return { id: userId };\n * }\n * ```\n */\nexport const CurrentUser = createParamDecorator(\n (data: string | undefined, ctx: ExecutionContext) => {\n const request = ctx.switchToHttp().getRequest();\n const user = request.user;\n\n if (!user) {\n return null;\n }\n\n return data ? user[data] : user;\n },\n);\n","import {\n IsEmail,\n IsString,\n Matches,\n MaxLength,\n MinLength,\n} from \"class-validator\";\n\n/**\n * Login request DTO\n */\nexport class LoginDto {\n @IsEmail({}, { message: \"Please provide a valid email address\" })\n email!: string;\n\n @IsString()\n @MinLength(1, { message: \"Password is required\" })\n password!: string;\n}\n\n/**\n * Registration request DTO\n */\nexport class RegisterDto {\n @IsEmail({}, { message: \"Please provide a valid email address\" })\n email!: string;\n\n @IsString()\n @MinLength(8, { message: \"Password must be at least 8 characters long\" })\n @MaxLength(128, { message: \"Password must be at most 128 characters long\" })\n @Matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)/, {\n message:\n \"Password must contain at least one lowercase letter, one uppercase letter, and one number\",\n })\n password!: string;\n\n @IsString()\n @MinLength(1, { message: \"Password confirmation is required\" })\n confirmPassword!: string;\n}\n\n/**\n * Refresh token request DTO\n */\nexport class RefreshTokenDto {\n @IsString()\n @MinLength(1, { message: \"Refresh token is required\" })\n refreshToken!: string;\n}\n\n/**\n * Token response DTO\n */\nexport class TokensResponseDto {\n accessToken!: string;\n refreshToken!: string;\n\n constructor(accessToken: string, refreshToken: string) {\n this.accessToken = accessToken;\n this.refreshToken = refreshToken;\n }\n}\n\n/**\n * Message response DTO\n */\nexport class MessageResponseDto {\n message!: string;\n\n constructor(message: string) {\n this.message = message;\n }\n}\n","import { ExecutionContext, Injectable } from \"@nestjs/common\";\nimport { Reflector } from \"@nestjs/core\";\nimport { AuthGuard } from \"@nestjs/passport\";\nimport { IS_PUBLIC_KEY } from \"../decorators\";\n\n/**\n * JWT Auth Guard for protecting routes\n * Uses the 'lenan-jwt' strategy to validate access tokens\n * Respects the @Public() decorator to skip authentication\n */\n@Injectable()\nexport class JwtAuthGuard extends AuthGuard(\"lenan-jwt\") {\n constructor(private reflector: Reflector) {\n super();\n }\n\n canActivate(context: ExecutionContext) {\n // Check if route is marked as public\n const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [\n context.getHandler(),\n context.getClass(),\n ]);\n\n if (isPublic) {\n return true;\n }\n\n return super.canActivate(context);\n }\n}\n","import { Injectable } from \"@nestjs/common\";\nimport { AuthGuard } from \"@nestjs/passport\";\n\n/**\n * Refresh Token Guard for protecting the refresh endpoint\n * Uses the 'lenan-jwt-refresh' strategy to validate refresh tokens\n */\n@Injectable()\nexport class RefreshTokenGuard extends AuthGuard(\"lenan-jwt-refresh\") {}\n","import type { AuthUser, BaseUser } from \"../../shared\";\n\n/**\n * User service interface that consumers must implement\n * This is the contract between the auth library and your application's user management\n */\nexport interface UserServiceInterface<TUser extends AuthUser = AuthUser> {\n /**\n * Find a user by their email address\n * Used during login to validate credentials\n */\n findByEmail(email: string): Promise<TUser | null>;\n\n /**\n * Find a user by their ID\n * Used for token validation and user retrieval\n */\n findById(id: string): Promise<TUser | null>;\n\n /**\n * Create a new user with hashed password\n * Called during registration\n */\n createUser(data: { email: string; passwordHash: string }): Promise<TUser>;\n\n /**\n * Update the user's hashed refresh token\n * Called after login/refresh to store the new refresh token\n * Pass null to clear the token (logout)\n */\n updateRefreshToken(userId: string, hashedToken: string | null): Promise<void>;\n\n /**\n * Validate that the provided refresh token matches the stored one\n * Should compare hashed values\n */\n validateRefreshToken(userId: string, hashedToken: string): Promise<boolean>;\n}\n\n/**\n * Injection token for the user service\n */\nexport const LENAN_USER_SERVICE = Symbol(\"LENAN_USER_SERVICE\");\n\n/**\n * Configuration options for the auth module\n */\nexport interface AuthModuleOptions {\n /** Secret key for signing JWT access tokens */\n jwtSecret: string;\n\n /** Access token expiration time (e.g., '15m', '1h') */\n jwtAccessExpiry?: string;\n\n /** Refresh token expiration time (e.g., '7d', '30d') */\n jwtRefreshExpiry?: string;\n\n /** Optional separate secret for refresh tokens (defaults to jwtSecret + '_refresh') */\n jwtRefreshSecret?: string;\n\n /** Whether to register the default AuthController (default: true) */\n useController?: boolean;\n\n /** Global route prefix for auth endpoints (default: 'auth') */\n routePrefix?: string;\n}\n\n/**\n * Async configuration options for the auth module\n */\nexport interface AuthModuleAsyncOptions {\n /**\n * Imports required for the factory/useClass\n */\n imports?: any[];\n\n /**\n * Factory function to create the options\n */\n useFactory?: (\n ...args: any[]\n ) => Promise<AuthModuleOptions> | AuthModuleOptions;\n\n /**\n * Dependencies to inject into the factory\n */\n inject?: any[];\n\n /**\n * Class that implements AuthOptionsFactory\n */\n useClass?: new (...args: any[]) => AuthOptionsFactory;\n\n /**\n * Existing provider to use\n */\n useExisting?: new (...args: any[]) => AuthOptionsFactory;\n}\n\n/**\n * Factory interface for creating auth options\n */\nexport interface AuthOptionsFactory {\n createAuthOptions(): Promise<AuthModuleOptions> | AuthModuleOptions;\n}\n\n/**\n * Injection token for auth module options\n */\nexport const LENAN_AUTH_OPTIONS = Symbol(\"LENAN_AUTH_OPTIONS\");\n\n/**\n * Request with user attached (after authentication)\n */\nexport interface AuthenticatedRequest<\n TUser extends BaseUser = BaseUser,\n> extends Request {\n user: TUser;\n}\n\n/**\n * JWT payload for access tokens\n */\nexport interface AccessTokenPayload {\n sub: string;\n email: string;\n type: \"access\";\n}\n\n/**\n * JWT payload for refresh tokens\n */\nexport interface RefreshTokenPayload {\n sub: string;\n email: string;\n type: \"refresh\";\n}\n","import {\n BadRequestException,\n ConflictException,\n Inject,\n Injectable,\n UnauthorizedException,\n} from \"@nestjs/common\";\nimport type { AuthTokens, AuthUser } from \"../../shared\";\nimport type { LoginDto, RegisterDto } from \"../dto\";\nimport { LENAN_USER_SERVICE, type UserServiceInterface } from \"../interfaces\";\nimport { JwtTokenService } from \"./jwt-token.service\";\nimport { PasswordService } from \"./password.service\";\n\n/**\n * Main authentication service\n * Orchestrates user authentication, registration, and token management\n */\n@Injectable()\nexport class AuthService {\n constructor(\n @Inject(LENAN_USER_SERVICE)\n private readonly userService: UserServiceInterface,\n private readonly passwordService: PasswordService,\n private readonly jwtTokenService: JwtTokenService,\n ) {}\n\n /**\n * Register a new user\n * @throws ConflictException if email already exists\n */\n async register(dto: RegisterDto): Promise<AuthTokens> {\n // Check if user already exists\n const existingUser = await this.userService.findByEmail(dto.email);\n if (existingUser) {\n throw new ConflictException(\"Email already registered\");\n }\n\n // Validate password confirmation\n if (dto.password !== dto.confirmPassword) {\n throw new BadRequestException(\"Passwords do not match\");\n }\n\n // Hash password and create user\n const passwordHash = await this.passwordService.hash(dto.password);\n const user = await this.userService.createUser({\n email: dto.email,\n passwordHash,\n });\n\n // Generate tokens\n const tokens = await this.jwtTokenService.generateTokens(\n user.id,\n user.email,\n );\n\n // Store hashed refresh token\n const hashedRefreshToken = await this.passwordService.hashToken(\n tokens.refreshToken,\n );\n await this.userService.updateRefreshToken(user.id, hashedRefreshToken);\n\n return tokens;\n }\n\n /**\n * Authenticate a user with email and password\n * @throws UnauthorizedException if credentials are invalid\n */\n async login(dto: LoginDto): Promise<AuthTokens> {\n const user = await this.userService.findByEmail(dto.email);\n if (!user) {\n throw new UnauthorizedException(\"Invalid credentials\");\n }\n\n const isPasswordValid = await this.passwordService.compare(\n dto.password,\n user.passwordHash,\n );\n if (!isPasswordValid) {\n throw new UnauthorizedException(\"Invalid credentials\");\n }\n\n // Generate tokens\n const tokens = await this.jwtTokenService.generateTokens(\n user.id,\n user.email,\n );\n\n // Store hashed refresh token\n const hashedRefreshToken = await this.passwordService.hashToken(\n tokens.refreshToken,\n );\n await this.userService.updateRefreshToken(user.id, hashedRefreshToken);\n\n return tokens;\n }\n\n /**\n * Refresh tokens using a valid refresh token\n * @throws UnauthorizedException if refresh token is invalid\n */\n async refreshTokens(\n userId: string,\n refreshToken: string,\n ): Promise<AuthTokens> {\n const user = await this.userService.findById(userId);\n if (!user || !user.hashedRefreshToken) {\n throw new UnauthorizedException(\"Invalid refresh token\");\n }\n\n // Verify the refresh token matches\n const isValid = await this.passwordService.compareToken(\n refreshToken,\n user.hashedRefreshToken,\n );\n if (!isValid) {\n throw new UnauthorizedException(\"Invalid refresh token\");\n }\n\n // Generate new tokens\n const tokens = await this.jwtTokenService.generateTokens(\n user.id,\n user.email,\n );\n\n // Store new hashed refresh token\n const hashedRefreshToken = await this.passwordService.hashToken(\n tokens.refreshToken,\n );\n await this.userService.updateRefreshToken(user.id, hashedRefreshToken);\n\n return tokens;\n }\n\n /**\n * Log out a user by clearing their refresh token\n */\n async logout(userId: string): Promise<void> {\n await this.userService.updateRefreshToken(userId, null);\n }\n\n /**\n * Validate a user exists by ID\n * Used by JWT strategy to validate token payloads\n */\n async validateUser(userId: string): Promise<AuthUser | null> {\n return this.userService.findById(userId);\n }\n\n /**\n * Get current user by ID (without sensitive fields)\n */\n async getCurrentUser(\n userId: string,\n ): Promise<Omit<AuthUser, \"passwordHash\" | \"hashedRefreshToken\"> | null> {\n const user = await this.userService.findById(userId);\n if (!user) {\n return null;\n }\n\n // Remove sensitive fields\n const { passwordHash, hashedRefreshToken, ...safeUser } = user;\n return safeUser;\n }\n}\n","import { Inject, Injectable } from \"@nestjs/common\";\nimport { JwtService } from \"@nestjs/jwt\";\nimport type { AuthTokens } from \"../../shared\";\nimport {\n LENAN_AUTH_OPTIONS,\n type AccessTokenPayload,\n type AuthModuleOptions,\n type RefreshTokenPayload,\n} from \"../interfaces\";\n\n/**\n * Service for JWT token generation and verification\n */\n@Injectable()\nexport class JwtTokenService {\n private readonly accessExpiry: string;\n private readonly refreshExpiry: string;\n private readonly accessSecret: string;\n private readonly refreshSecret: string;\n\n constructor(\n private readonly jwtService: JwtService,\n @Inject(LENAN_AUTH_OPTIONS) options: AuthModuleOptions,\n ) {\n this.accessSecret = options.jwtSecret;\n this.refreshSecret =\n options.jwtRefreshSecret ?? `${options.jwtSecret}_refresh`;\n this.accessExpiry = options.jwtAccessExpiry ?? \"15m\";\n this.refreshExpiry = options.jwtRefreshExpiry ?? \"7d\";\n }\n\n /**\n * Generate access and refresh tokens for a user\n */\n async generateTokens(userId: string, email: string): Promise<AuthTokens> {\n const [accessToken, refreshToken] = await Promise.all([\n this.generateAccessToken(userId, email),\n this.generateRefreshToken(userId, email),\n ]);\n\n return { accessToken, refreshToken };\n }\n\n /**\n * Generate an access token\n */\n async generateAccessToken(userId: string, email: string): Promise<string> {\n const payload: AccessTokenPayload = {\n sub: userId,\n email,\n type: \"access\",\n };\n\n return this.jwtService.signAsync(payload, {\n secret: this.accessSecret,\n expiresIn: this.accessExpiry as any,\n });\n }\n\n /**\n * Generate a refresh token\n */\n async generateRefreshToken(userId: string, email: string): Promise<string> {\n const payload: RefreshTokenPayload = {\n sub: userId,\n email,\n type: \"refresh\",\n };\n\n return this.jwtService.signAsync(payload, {\n secret: this.refreshSecret,\n expiresIn: this.refreshExpiry as any,\n });\n }\n\n /**\n * Verify an access token and return its payload\n */\n async verifyAccessToken(token: string): Promise<AccessTokenPayload | null> {\n try {\n const payload = await this.jwtService.verifyAsync<AccessTokenPayload>(\n token,\n {\n secret: this.accessSecret,\n },\n );\n\n if (payload.type !== \"access\") {\n return null;\n }\n\n return payload;\n } catch {\n return null;\n }\n }\n\n /**\n * Verify a refresh token and return its payload\n */\n async verifyRefreshToken(token: string): Promise<RefreshTokenPayload | null> {\n try {\n const payload = await this.jwtService.verifyAsync<RefreshTokenPayload>(\n token,\n {\n secret: this.refreshSecret,\n },\n );\n\n if (payload.type !== \"refresh\") {\n return null;\n }\n\n return payload;\n } catch {\n return null;\n }\n }\n\n /**\n * Decode a token without verification (useful for debugging)\n */\n decodeToken<T = any>(token: string): T | null {\n try {\n return this.jwtService.decode(token) as T;\n } catch {\n return null;\n }\n }\n}\n","import { Injectable } from \"@nestjs/common\";\nimport * as bcrypt from \"bcryptjs\";\n\n/**\n * Service for password hashing and comparison using bcrypt\n */\n@Injectable()\nexport class PasswordService {\n private readonly saltRounds = 12;\n\n /**\n * Hash a plain text password\n */\n async hash(password: string): Promise<string> {\n return bcrypt.hash(password, this.saltRounds);\n }\n\n /**\n * Compare a plain text password with a hash\n */\n async compare(password: string, hash: string): Promise<boolean> {\n return bcrypt.compare(password, hash);\n }\n\n /**\n * Hash a refresh token for storage\n * Uses fewer rounds since refresh tokens are already random\n */\n async hashToken(token: string): Promise<string> {\n return bcrypt.hash(token, 10);\n }\n\n /**\n * Compare a refresh token with its hash\n */\n async compareToken(token: string, hash: string): Promise<boolean> {\n return bcrypt.compare(token, hash);\n }\n}\n","import { Inject, Injectable, UnauthorizedException } from \"@nestjs/common\";\nimport { PassportStrategy } from \"@nestjs/passport\";\nimport { ExtractJwt, Strategy } from \"passport-jwt\";\nimport {\n LENAN_AUTH_OPTIONS,\n LENAN_USER_SERVICE,\n type AccessTokenPayload,\n type AuthModuleOptions,\n type UserServiceInterface,\n} from \"../interfaces\";\n\n/**\n * JWT Strategy for validating access tokens\n * Extracts token from Authorization header and validates against the user service\n */\n@Injectable()\nexport class JwtStrategy extends PassportStrategy(Strategy, \"lenan-jwt\") {\n constructor(\n @Inject(LENAN_AUTH_OPTIONS) options: AuthModuleOptions,\n @Inject(LENAN_USER_SERVICE)\n private readonly userService: UserServiceInterface,\n ) {\n super({\n jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),\n ignoreExpiration: false,\n secretOrKey: options.jwtSecret,\n });\n }\n\n /**\n * Validate the JWT payload and return the user\n * This is called by Passport after successfully verifying the token signature\n */\n async validate(payload: AccessTokenPayload) {\n // Ensure this is an access token, not a refresh token\n if (payload.type !== \"access\") {\n throw new UnauthorizedException(\"Invalid token type\");\n }\n\n const user = await this.userService.findById(payload.sub);\n if (!user) {\n throw new UnauthorizedException(\"User not found\");\n }\n\n // Return user without sensitive fields\n const { passwordHash, hashedRefreshToken, ...safeUser } = user;\n return safeUser;\n }\n}\n","import { Inject, Injectable, UnauthorizedException } from \"@nestjs/common\";\nimport { PassportStrategy } from \"@nestjs/passport\";\nimport { Request } from \"express\";\nimport { ExtractJwt, Strategy } from \"passport-jwt\";\nimport {\n LENAN_AUTH_OPTIONS,\n LENAN_USER_SERVICE,\n type AuthModuleOptions,\n type RefreshTokenPayload,\n type UserServiceInterface,\n} from \"../interfaces\";\n\n/**\n * Refresh Token Strategy for validating refresh tokens\n * Extracts token from request body and validates it\n */\n@Injectable()\nexport class RefreshTokenStrategy extends PassportStrategy(\n Strategy,\n \"lenan-jwt-refresh\",\n) {\n constructor(\n @Inject(LENAN_AUTH_OPTIONS) options: AuthModuleOptions,\n @Inject(LENAN_USER_SERVICE)\n private readonly userService: UserServiceInterface,\n ) {\n const refreshSecret =\n options.jwtRefreshSecret ?? `${options.jwtSecret}_refresh`;\n\n super({\n jwtFromRequest: ExtractJwt.fromBodyField(\"refreshToken\"),\n ignoreExpiration: false,\n secretOrKey: refreshSecret,\n passReqToCallback: true,\n });\n }\n\n /**\n * Validate the refresh token payload\n * Returns user with the refresh token attached for further validation\n */\n async validate(req: Request, payload: RefreshTokenPayload) {\n // Ensure this is a refresh token\n if (payload.type !== \"refresh\") {\n throw new UnauthorizedException(\"Invalid token type\");\n }\n\n const user = await this.userService.findById(payload.sub);\n if (!user) {\n throw new UnauthorizedException(\"User not found\");\n }\n\n if (!user.hashedRefreshToken) {\n throw new UnauthorizedException(\"Refresh token has been revoked\");\n }\n\n // Return user with refresh token for AuthService to validate\n const refreshToken = req.body?.refreshToken;\n return { ...user, refreshToken };\n }\n}\n"],"mappings":"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","names":["Injectable","AuthGuard","AuthGuard","Injectable","Injectable","Injectable","Inject","Injectable","Injectable","Inject","Injectable","hash","Injectable","Inject","Injectable","UnauthorizedException","UnauthorizedException","Injectable","Inject","Inject","Injectable","UnauthorizedException","PassportStrategy","ExtractJwt","Strategy","PassportStrategy","Strategy","ExtractJwt","UnauthorizedException","Injectable","Inject"]}
@@ -0,0 +1,560 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/react/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ AuthClient: () => AuthClient,
24
+ AuthProvider: () => AuthProvider,
25
+ DEFAULT_AUTH_ENDPOINTS: () => DEFAULT_AUTH_ENDPOINTS,
26
+ LocalStorageAdapter: () => LocalStorageAdapter,
27
+ MemoryStorageAdapter: () => MemoryStorageAdapter,
28
+ TOKEN_STORAGE_KEYS: () => TOKEN_STORAGE_KEYS,
29
+ createAuthClient: () => createAuthClient,
30
+ useAuth: () => useAuth,
31
+ useIsAuthenticated: () => useIsAuthenticated,
32
+ useUser: () => useUser
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/react/auth-provider.tsx
37
+ var import_react = require("react");
38
+
39
+ // src/shared/types.ts
40
+ var DEFAULT_AUTH_ENDPOINTS = {
41
+ login: "/auth/login",
42
+ register: "/auth/register",
43
+ refresh: "/auth/refresh",
44
+ logout: "/auth/logout",
45
+ me: "/auth/me"
46
+ };
47
+ var TOKEN_STORAGE_KEYS = {
48
+ ACCESS_TOKEN: "lenan_access_token",
49
+ REFRESH_TOKEN: "lenan_refresh_token"
50
+ };
51
+
52
+ // src/react/auth-client.ts
53
+ var AuthClient = class {
54
+ config;
55
+ endpoints;
56
+ accessTokenKey;
57
+ refreshTokenKey;
58
+ isRefreshing = false;
59
+ refreshPromise = null;
60
+ constructor(config, endpoints) {
61
+ this.config = config;
62
+ this.endpoints = { ...DEFAULT_AUTH_ENDPOINTS, ...endpoints };
63
+ this.accessTokenKey = config.accessTokenKey ?? TOKEN_STORAGE_KEYS.ACCESS_TOKEN;
64
+ this.refreshTokenKey = config.refreshTokenKey ?? TOKEN_STORAGE_KEYS.REFRESH_TOKEN;
65
+ }
66
+ /**
67
+ * Make an authenticated request
68
+ * Automatically attaches access token and handles 401 refresh
69
+ */
70
+ async request(path, options = {}) {
71
+ const accessToken = await this.getAccessToken();
72
+ const headers = {
73
+ "Content-Type": "application/json",
74
+ ...this.config.headers,
75
+ ...options.headers || {}
76
+ };
77
+ if (accessToken) {
78
+ headers["Authorization"] = `Bearer ${accessToken}`;
79
+ }
80
+ const response = await fetch(`${this.config.baseUrl}${path}`, {
81
+ ...options,
82
+ headers
83
+ });
84
+ if (response.status === 401 && accessToken) {
85
+ const refreshed = await this.tryRefreshToken();
86
+ if (refreshed) {
87
+ headers["Authorization"] = `Bearer ${refreshed.accessToken}`;
88
+ const retryResponse = await fetch(`${this.config.baseUrl}${path}`, {
89
+ ...options,
90
+ headers
91
+ });
92
+ return this.handleResponse(retryResponse);
93
+ }
94
+ }
95
+ return this.handleResponse(response);
96
+ }
97
+ /**
98
+ * Login with email and password
99
+ */
100
+ async login(email, password) {
101
+ const response = await fetch(
102
+ `${this.config.baseUrl}${this.endpoints.login}`,
103
+ {
104
+ method: "POST",
105
+ headers: {
106
+ "Content-Type": "application/json",
107
+ ...this.config.headers
108
+ },
109
+ body: JSON.stringify({ email, password })
110
+ }
111
+ );
112
+ const tokens = await this.handleResponse(response);
113
+ await this.storeTokens(tokens);
114
+ return tokens;
115
+ }
116
+ /**
117
+ * Register a new user
118
+ */
119
+ async register(email, password, confirmPassword) {
120
+ const response = await fetch(
121
+ `${this.config.baseUrl}${this.endpoints.register}`,
122
+ {
123
+ method: "POST",
124
+ headers: {
125
+ "Content-Type": "application/json",
126
+ ...this.config.headers
127
+ },
128
+ body: JSON.stringify({
129
+ email,
130
+ password,
131
+ confirmPassword: confirmPassword ?? password
132
+ })
133
+ }
134
+ );
135
+ const tokens = await this.handleResponse(response);
136
+ await this.storeTokens(tokens);
137
+ return tokens;
138
+ }
139
+ /**
140
+ * Refresh the access token
141
+ */
142
+ async refresh() {
143
+ const refreshToken = await this.getRefreshToken();
144
+ if (!refreshToken) {
145
+ return null;
146
+ }
147
+ try {
148
+ const response = await fetch(
149
+ `${this.config.baseUrl}${this.endpoints.refresh}`,
150
+ {
151
+ method: "POST",
152
+ headers: {
153
+ "Content-Type": "application/json",
154
+ ...this.config.headers
155
+ },
156
+ body: JSON.stringify({ refreshToken })
157
+ }
158
+ );
159
+ if (!response.ok) {
160
+ await this.clearTokens();
161
+ return null;
162
+ }
163
+ const tokens = await response.json();
164
+ await this.storeTokens(tokens);
165
+ return tokens;
166
+ } catch {
167
+ await this.clearTokens();
168
+ return null;
169
+ }
170
+ }
171
+ /**
172
+ * Logout the user
173
+ */
174
+ async logout() {
175
+ const accessToken = await this.getAccessToken();
176
+ if (accessToken) {
177
+ try {
178
+ await fetch(`${this.config.baseUrl}${this.endpoints.logout}`, {
179
+ method: "POST",
180
+ headers: {
181
+ "Content-Type": "application/json",
182
+ Authorization: `Bearer ${accessToken}`,
183
+ ...this.config.headers
184
+ }
185
+ });
186
+ } catch {
187
+ }
188
+ }
189
+ await this.clearTokens();
190
+ }
191
+ /**
192
+ * Get the current user
193
+ */
194
+ async me() {
195
+ const accessToken = await this.getAccessToken();
196
+ if (!accessToken) {
197
+ return null;
198
+ }
199
+ try {
200
+ const response = await this.request(this.endpoints.me);
201
+ return response;
202
+ } catch {
203
+ return null;
204
+ }
205
+ }
206
+ /**
207
+ * Check if user has tokens stored
208
+ */
209
+ async hasTokens() {
210
+ const accessToken = await this.getAccessToken();
211
+ return !!accessToken;
212
+ }
213
+ /**
214
+ * Get stored tokens
215
+ */
216
+ async getTokens() {
217
+ const [accessToken, refreshToken] = await Promise.all([
218
+ this.getAccessToken(),
219
+ this.getRefreshToken()
220
+ ]);
221
+ if (!accessToken || !refreshToken) {
222
+ return null;
223
+ }
224
+ return { accessToken, refreshToken };
225
+ }
226
+ async tryRefreshToken() {
227
+ if (this.isRefreshing) {
228
+ return this.refreshPromise;
229
+ }
230
+ this.isRefreshing = true;
231
+ this.refreshPromise = this.refresh();
232
+ try {
233
+ const result = await this.refreshPromise;
234
+ return result;
235
+ } finally {
236
+ this.isRefreshing = false;
237
+ this.refreshPromise = null;
238
+ }
239
+ }
240
+ async handleResponse(response) {
241
+ if (!response.ok) {
242
+ let errorMessage = `Request failed with status ${response.status}`;
243
+ try {
244
+ const errorBody = await response.json();
245
+ if (typeof errorBody === "object" && errorBody !== null) {
246
+ const body = errorBody;
247
+ if (typeof body.message === "string") {
248
+ errorMessage = body.message;
249
+ } else if (typeof body.error === "string") {
250
+ errorMessage = body.error;
251
+ }
252
+ }
253
+ } catch {
254
+ }
255
+ throw new Error(errorMessage);
256
+ }
257
+ const text = await response.text();
258
+ if (!text) {
259
+ return {};
260
+ }
261
+ return JSON.parse(text);
262
+ }
263
+ async getAccessToken() {
264
+ return Promise.resolve(
265
+ this.config.tokenStorage.getItem(this.accessTokenKey)
266
+ );
267
+ }
268
+ async getRefreshToken() {
269
+ return Promise.resolve(
270
+ this.config.tokenStorage.getItem(this.refreshTokenKey)
271
+ );
272
+ }
273
+ async storeTokens(tokens) {
274
+ await Promise.all([
275
+ this.config.tokenStorage.setItem(this.accessTokenKey, tokens.accessToken),
276
+ this.config.tokenStorage.setItem(
277
+ this.refreshTokenKey,
278
+ tokens.refreshToken
279
+ )
280
+ ]);
281
+ }
282
+ async clearTokens() {
283
+ await Promise.all([
284
+ this.config.tokenStorage.removeItem(this.accessTokenKey),
285
+ this.config.tokenStorage.removeItem(this.refreshTokenKey)
286
+ ]);
287
+ }
288
+ };
289
+ function createAuthClient(config, endpoints) {
290
+ return new AuthClient(config, endpoints);
291
+ }
292
+
293
+ // src/react/storage.ts
294
+ var LocalStorageAdapter = class {
295
+ memoryStorage = /* @__PURE__ */ new Map();
296
+ isLocalStorageAvailable;
297
+ constructor() {
298
+ this.isLocalStorageAvailable = this.checkLocalStorage();
299
+ }
300
+ checkLocalStorage() {
301
+ try {
302
+ const testKey = "__lenan_auth_test__";
303
+ localStorage.setItem(testKey, testKey);
304
+ localStorage.removeItem(testKey);
305
+ return true;
306
+ } catch {
307
+ return false;
308
+ }
309
+ }
310
+ getItem(key) {
311
+ if (this.isLocalStorageAvailable) {
312
+ return localStorage.getItem(key);
313
+ }
314
+ return this.memoryStorage.get(key) ?? null;
315
+ }
316
+ setItem(key, value) {
317
+ if (this.isLocalStorageAvailable) {
318
+ localStorage.setItem(key, value);
319
+ } else {
320
+ this.memoryStorage.set(key, value);
321
+ }
322
+ }
323
+ removeItem(key) {
324
+ if (this.isLocalStorageAvailable) {
325
+ localStorage.removeItem(key);
326
+ } else {
327
+ this.memoryStorage.delete(key);
328
+ }
329
+ }
330
+ };
331
+ var MemoryStorageAdapter = class {
332
+ storage = /* @__PURE__ */ new Map();
333
+ getItem(key) {
334
+ return this.storage.get(key) ?? null;
335
+ }
336
+ setItem(key, value) {
337
+ this.storage.set(key, value);
338
+ }
339
+ removeItem(key) {
340
+ this.storage.delete(key);
341
+ }
342
+ clear() {
343
+ this.storage.clear();
344
+ }
345
+ };
346
+
347
+ // src/react/auth-provider.tsx
348
+ var import_jsx_runtime = require("react/jsx-runtime");
349
+ var AuthContext = (0, import_react.createContext)(null);
350
+ function AuthProvider({
351
+ baseUrl,
352
+ tokenStorage,
353
+ endpoints,
354
+ headers,
355
+ children,
356
+ onAuthStateChange,
357
+ autoRefresh = true
358
+ }) {
359
+ const [state, setState] = (0, import_react.useState)({
360
+ user: null,
361
+ tokens: null,
362
+ isLoading: true,
363
+ isAuthenticated: false,
364
+ error: null
365
+ });
366
+ const client = (0, import_react.useMemo)(() => {
367
+ const storage = tokenStorage ?? new LocalStorageAdapter();
368
+ return new AuthClient(
369
+ { baseUrl, tokenStorage: storage, headers },
370
+ endpoints
371
+ );
372
+ }, [baseUrl, tokenStorage, endpoints, headers]);
373
+ const updateState = (0, import_react.useCallback)(
374
+ (updates) => {
375
+ setState((prev) => {
376
+ const newState = { ...prev, ...updates };
377
+ onAuthStateChange?.(newState);
378
+ return newState;
379
+ });
380
+ },
381
+ [onAuthStateChange]
382
+ );
383
+ (0, import_react.useEffect)(() => {
384
+ const initAuth = async () => {
385
+ try {
386
+ const hasTokens = await client.hasTokens();
387
+ if (!hasTokens) {
388
+ updateState({ isLoading: false });
389
+ return;
390
+ }
391
+ const user = await client.me();
392
+ if (user) {
393
+ const tokens = await client.getTokens();
394
+ updateState({
395
+ user,
396
+ tokens,
397
+ isAuthenticated: true,
398
+ isLoading: false
399
+ });
400
+ } else if (autoRefresh) {
401
+ const newTokens = await client.refresh();
402
+ if (newTokens) {
403
+ const refreshedUser = await client.me();
404
+ updateState({
405
+ user: refreshedUser,
406
+ tokens: newTokens,
407
+ isAuthenticated: !!refreshedUser,
408
+ isLoading: false
409
+ });
410
+ } else {
411
+ updateState({ isLoading: false });
412
+ }
413
+ } else {
414
+ updateState({ isLoading: false });
415
+ }
416
+ } catch (error) {
417
+ updateState({
418
+ isLoading: false,
419
+ error: error instanceof Error ? error.message : "Failed to initialize auth"
420
+ });
421
+ }
422
+ };
423
+ initAuth();
424
+ }, [client, autoRefresh, updateState]);
425
+ const login = (0, import_react.useCallback)(
426
+ async (email, password) => {
427
+ updateState({ isLoading: true, error: null });
428
+ try {
429
+ const tokens = await client.login(email, password);
430
+ const user = await client.me();
431
+ updateState({
432
+ user,
433
+ tokens,
434
+ isAuthenticated: true,
435
+ isLoading: false
436
+ });
437
+ } catch (error) {
438
+ updateState({
439
+ isLoading: false,
440
+ error: error instanceof Error ? error.message : "Login failed"
441
+ });
442
+ throw error;
443
+ }
444
+ },
445
+ [client, updateState]
446
+ );
447
+ const register = (0, import_react.useCallback)(
448
+ async (email, password, confirmPassword) => {
449
+ updateState({ isLoading: true, error: null });
450
+ try {
451
+ const tokens = await client.register(email, password, confirmPassword);
452
+ const user = await client.me();
453
+ updateState({
454
+ user,
455
+ tokens,
456
+ isAuthenticated: true,
457
+ isLoading: false
458
+ });
459
+ } catch (error) {
460
+ updateState({
461
+ isLoading: false,
462
+ error: error instanceof Error ? error.message : "Registration failed"
463
+ });
464
+ throw error;
465
+ }
466
+ },
467
+ [client, updateState]
468
+ );
469
+ const logout = (0, import_react.useCallback)(async () => {
470
+ updateState({ isLoading: true, error: null });
471
+ try {
472
+ await client.logout();
473
+ updateState({
474
+ user: null,
475
+ tokens: null,
476
+ isAuthenticated: false,
477
+ isLoading: false
478
+ });
479
+ } catch (error) {
480
+ updateState({
481
+ user: null,
482
+ tokens: null,
483
+ isAuthenticated: false,
484
+ isLoading: false,
485
+ error: error instanceof Error ? error.message : "Logout failed"
486
+ });
487
+ }
488
+ }, [client, updateState]);
489
+ const refreshToken = (0, import_react.useCallback)(async () => {
490
+ try {
491
+ const tokens = await client.refresh();
492
+ if (tokens) {
493
+ const user = await client.me();
494
+ updateState({
495
+ user,
496
+ tokens,
497
+ isAuthenticated: !!user
498
+ });
499
+ } else {
500
+ updateState({
501
+ user: null,
502
+ tokens: null,
503
+ isAuthenticated: false
504
+ });
505
+ }
506
+ } catch (error) {
507
+ updateState({
508
+ user: null,
509
+ tokens: null,
510
+ isAuthenticated: false,
511
+ error: error instanceof Error ? error.message : "Token refresh failed"
512
+ });
513
+ }
514
+ }, [client, updateState]);
515
+ const clearError = (0, import_react.useCallback)(() => {
516
+ updateState({ error: null });
517
+ }, [updateState]);
518
+ const contextValue = (0, import_react.useMemo)(
519
+ () => ({
520
+ ...state,
521
+ login,
522
+ register,
523
+ logout,
524
+ refreshToken,
525
+ clearError,
526
+ client
527
+ }),
528
+ [state, login, register, logout, refreshToken, clearError, client]
529
+ );
530
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(AuthContext.Provider, { value: contextValue, children });
531
+ }
532
+ function useAuth() {
533
+ const context = (0, import_react.useContext)(AuthContext);
534
+ if (!context) {
535
+ throw new Error("useAuth must be used within an AuthProvider");
536
+ }
537
+ return context;
538
+ }
539
+ function useUser() {
540
+ const { user } = useAuth();
541
+ return user;
542
+ }
543
+ function useIsAuthenticated() {
544
+ const { isAuthenticated, isLoading } = useAuth();
545
+ return isAuthenticated && !isLoading;
546
+ }
547
+ // Annotate the CommonJS export names for ESM import in node:
548
+ 0 && (module.exports = {
549
+ AuthClient,
550
+ AuthProvider,
551
+ DEFAULT_AUTH_ENDPOINTS,
552
+ LocalStorageAdapter,
553
+ MemoryStorageAdapter,
554
+ TOKEN_STORAGE_KEYS,
555
+ createAuthClient,
556
+ useAuth,
557
+ useIsAuthenticated,
558
+ useUser
559
+ });
560
+ //# sourceMappingURL=index.cjs.map