95 lines
3.0 KiB
TypeScript
95 lines
3.0 KiB
TypeScript
import { CanActivate, ExecutionContext, Injectable, Inject, Logger } from '@nestjs/common';
|
|
import { Request } from 'express';
|
|
import { JwtService } from '@nestjs/jwt';
|
|
import { ConfigService } from '@nestjs/config';
|
|
import { ITokenPayload } from '../interfaces/IToken-payload';
|
|
|
|
export interface UserOptionalAuthRequest extends Request {
|
|
userId?: string;
|
|
restId?: string;
|
|
slug?: string;
|
|
}
|
|
|
|
@Injectable()
|
|
export class OptionalAuthGuard implements CanActivate {
|
|
private readonly logger = new Logger(OptionalAuthGuard.name);
|
|
|
|
constructor(
|
|
@Inject(JwtService)
|
|
private readonly jwtService: JwtService,
|
|
@Inject(ConfigService)
|
|
private readonly configService: ConfigService,
|
|
) {}
|
|
|
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
const request = context.switchToHttp().getRequest<UserOptionalAuthRequest>();
|
|
const token = this.extractTokenFromHeader(request);
|
|
const slug = this.extractSlugFromHeader(request);
|
|
|
|
// If no token is provided, allow the request without authentication
|
|
if (!slug) {
|
|
console.log('No slug provided');
|
|
this.logger.warn('No slug provided');
|
|
return false;
|
|
}
|
|
if (!token) {
|
|
request['slug'] = slug;
|
|
return true;
|
|
}
|
|
|
|
// Try to verify the token if it exists
|
|
try {
|
|
const secret = this.configService.getOrThrow<string>('JWT_SECRET');
|
|
const payload = await this.jwtService.verifyAsync<ITokenPayload>(token, {
|
|
secret,
|
|
});
|
|
|
|
// Validate payload structure
|
|
if (!payload.userId || !payload.restId) {
|
|
this.logger.warn('Invalid token payload structure', payload);
|
|
// Allow request but don't set user info
|
|
request['slug'] = slug;
|
|
return true;
|
|
}
|
|
|
|
// Validate slug if provided
|
|
if (slug && payload.slug !== slug) {
|
|
this.logger.warn('Token slug does not match header slug', {
|
|
tokenSlug: payload.slug,
|
|
headerSlug: slug,
|
|
});
|
|
// Allow request but don't set user info
|
|
request['slug'] = slug;
|
|
return true;
|
|
}
|
|
|
|
// Token is valid, set user info
|
|
request['userId'] = payload.userId;
|
|
request['restId'] = payload.restId;
|
|
request['slug'] = slug;
|
|
return true;
|
|
} catch (err) {
|
|
// Token is invalid or expired, but allow the request anyway
|
|
this.logger.debug('Token verification failed in OptionalAuthGuard', {
|
|
error: err instanceof Error ? err.message : 'Unknown error',
|
|
});
|
|
// Set restId from slug
|
|
|
|
request['slug'] = slug;
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
private extractTokenFromHeader(request: Request): string | undefined {
|
|
const [type, token] = request.headers.authorization?.split(' ') ?? [];
|
|
return type === 'Bearer' ? token : undefined;
|
|
}
|
|
|
|
private extractSlugFromHeader(request: Request): string | undefined {
|
|
// Fastify normalizes headers to lowercase, so check both cases
|
|
const slug = (request.headers['x-slug'] || request.headers['X-Slug']) as string;
|
|
return slug;
|
|
}
|
|
}
|