Files
donutbrowser/donut-sync/src/auth/auth.guard.ts
T
2026-03-28 23:31:20 +04:00

83 lines
2.6 KiB
TypeScript

import {
type CanActivate,
type ExecutionContext,
Injectable,
Logger,
UnauthorizedException,
} from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import type { Request } from "express";
import * as jwt from "jsonwebtoken";
import type { UserContext } from "./user-context.interface.js";
@Injectable()
export class AuthGuard implements CanActivate {
private readonly logger = new Logger(AuthGuard.name);
private jwtPublicKey: string | null = null;
constructor(private configService: ConfigService) {
const publicKey = this.configService.get<string>("SYNC_JWT_PUBLIC_KEY");
if (publicKey) {
this.jwtPublicKey = publicKey.replace(/\\n/g, "\n");
this.logger.log("JWT public key configured — cloud auth enabled");
}
}
canActivate(context: ExecutionContext): boolean {
const request = context.switchToHttp().getRequest<Request>();
const authHeader = request.headers.authorization;
if (!authHeader?.startsWith("Bearer ")) {
throw new UnauthorizedException(
"Missing or invalid authorization header",
);
}
const token = authHeader.substring(7);
// Try SYNC_TOKEN first (self-hosted mode)
const expectedToken = this.configService.get<string>("SYNC_TOKEN");
if (expectedToken && token === expectedToken) {
(request as unknown as Record<string, unknown>).user = {
mode: "self-hosted",
prefix: "",
teamPrefix: null,
profileLimit: 0,
teamProfileLimit: 0,
} satisfies UserContext;
return true;
}
// Try JWT verification (cloud mode)
if (this.jwtPublicKey) {
try {
const decoded = jwt.verify(token, this.jwtPublicKey, {
algorithms: ["RS256"],
}) as jwt.JwtPayload;
(request as unknown as Record<string, unknown>).user = {
mode: "cloud",
prefix: decoded.prefix || `users/${decoded.sub}/`,
teamPrefix: decoded.teamPrefix || null,
profileLimit: decoded.profileLimit || 0,
teamProfileLimit: decoded.teamProfileLimit || 0,
} satisfies UserContext;
return true;
} catch (err) {
this.logger.warn(
`JWT verification failed: ${err instanceof Error ? err.message : err}`,
);
}
}
// If SYNC_TOKEN is configured but didn't match, or JWT failed
if (!expectedToken && !this.jwtPublicKey) {
throw new UnauthorizedException(
"No auth method configured on server (set SYNC_TOKEN or SYNC_JWT_PUBLIC_KEY)",
);
}
throw new UnauthorizedException("Invalid sync token or JWT");
}
}