84 lines
2.5 KiB
TypeScript
84 lines
2.5 KiB
TypeScript
import {
|
|
Injectable,
|
|
CanActivate,
|
|
ExecutionContext,
|
|
UnauthorizedException,
|
|
} from '@nestjs/common';
|
|
import { Reflector } from '@nestjs/core';
|
|
import { JwtService } from '@nestjs/jwt';
|
|
import { AuthService } from '../auth.service';
|
|
import { SupabaseService } from '../supabase.service';
|
|
import { UsersService } from '../../users/users.service';
|
|
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
|
|
import { JwtPayload } from '../auth.service';
|
|
|
|
@Injectable()
|
|
export class JwtAuthGuard implements CanActivate {
|
|
constructor(
|
|
private reflector: Reflector,
|
|
private jwtService: JwtService,
|
|
private authService: AuthService,
|
|
private supabaseService: SupabaseService,
|
|
private usersService: UsersService
|
|
) {}
|
|
|
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
|
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
|
|
context.getHandler(),
|
|
context.getClass(),
|
|
]);
|
|
|
|
if (isPublic) {
|
|
return true;
|
|
}
|
|
|
|
const request = context.switchToHttp().getRequest();
|
|
const authHeader = request.headers.authorization;
|
|
|
|
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
throw new UnauthorizedException('Missing or invalid authorization header');
|
|
}
|
|
|
|
const token = authHeader.replace('Bearer ', '');
|
|
|
|
try {
|
|
// 1) Try our own JWT (from POST /auth/exchange)
|
|
try {
|
|
const payload = this.jwtService.verify<JwtPayload>(token);
|
|
const user = await this.authService.validateJwtPayload(payload);
|
|
if (user) {
|
|
request.user = user;
|
|
return true;
|
|
}
|
|
} catch {
|
|
// Not our JWT or expired — try Supabase below
|
|
}
|
|
|
|
// 2) Fallback: Supabase access_token (for backward compatibility)
|
|
const supabaseUser = await this.supabaseService.verifyToken(token);
|
|
if (!supabaseUser) {
|
|
throw new UnauthorizedException('Invalid token');
|
|
}
|
|
|
|
let user = await this.usersService.findBySupabaseId(supabaseUser.id);
|
|
if (!user) {
|
|
user = await this.usersService.create({
|
|
supabaseId: supabaseUser.id,
|
|
email: supabaseUser.email!,
|
|
name:
|
|
supabaseUser.user_metadata?.full_name ||
|
|
supabaseUser.user_metadata?.name ||
|
|
null,
|
|
avatarUrl: supabaseUser.user_metadata?.avatar_url || null,
|
|
});
|
|
}
|
|
|
|
request.user = user;
|
|
return true;
|
|
} catch (error) {
|
|
if (error instanceof UnauthorizedException) throw error;
|
|
throw new UnauthorizedException('Token validation failed');
|
|
}
|
|
}
|
|
}
|