mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-05-07 19:06:47 +02:00
83 lines
2.6 KiB
TypeScript
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");
|
|
}
|
|
}
|