78 lines
2.3 KiB
TypeScript
78 lines
2.3 KiB
TypeScript
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { PassportStrategy } from '@nestjs/passport';
|
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
|
import { AuthService } from './auth.service';
|
|
|
|
/**
|
|
* JWT Payload interface matching the token structure
|
|
*/
|
|
export interface JwtPayload {
|
|
sub: string; // user ID
|
|
email: string;
|
|
role: string;
|
|
organizationId: string;
|
|
type: 'access' | 'refresh';
|
|
iat?: number; // issued at
|
|
exp?: number; // expiration
|
|
}
|
|
|
|
/**
|
|
* JWT Strategy for Passport authentication
|
|
*
|
|
* This strategy:
|
|
* - Extracts JWT from Authorization Bearer header
|
|
* - Validates the token signature using the secret
|
|
* - Validates the payload and retrieves the user
|
|
* - Injects the user into the request object
|
|
*
|
|
* @see https://docs.nestjs.com/security/authentication#implementing-passport-jwt
|
|
*/
|
|
@Injectable()
|
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
|
constructor(
|
|
private readonly configService: ConfigService,
|
|
private readonly authService: AuthService
|
|
) {
|
|
super({
|
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
|
ignoreExpiration: false,
|
|
secretOrKey: configService.get<string>('JWT_SECRET'),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Validate JWT payload and return user object
|
|
*
|
|
* This method is called automatically by Passport after the JWT is verified.
|
|
* If this method throws an error or returns null/undefined, authentication fails.
|
|
*
|
|
* @param payload - Decoded JWT payload
|
|
* @returns User object to be attached to request.user
|
|
* @throws UnauthorizedException if user is invalid or inactive
|
|
*/
|
|
async validate(payload: JwtPayload) {
|
|
// Only accept access tokens (not refresh tokens)
|
|
if (payload.type !== 'access') {
|
|
throw new UnauthorizedException('Invalid token type');
|
|
}
|
|
|
|
// Validate user exists and is active
|
|
const user = await this.authService.validateUser(payload);
|
|
|
|
if (!user) {
|
|
throw new UnauthorizedException('User not found or inactive');
|
|
}
|
|
|
|
// This object will be attached to request.user
|
|
return {
|
|
id: user.id,
|
|
email: user.email,
|
|
role: user.role,
|
|
organizationId: user.organizationId,
|
|
firstName: user.firstName,
|
|
lastName: user.lastName,
|
|
};
|
|
}
|
|
}
|