Files
paperlessmanager/paperless-backend/src/auth/api-key.guard.ts
T
bjoernpoettker 156b0401b3
Build and Push Multi-Platform Images / build-and-push (push) Successful in 30s
fix(auth): JWT nicht als API-Key prüfen (Bearer-Fallback nur für pm_-Keys)
Beim JWT-Ablauf fällt der JwtOrApiKeyGuard auf den ApiKeyGuard zurück. Dieser
nahm bisher jedes Authorization-Bearer-Token als API-Key-Kandidaten – also auch
das (abgelaufene) JWT – und loggte eine irreführende "Invalid API Key"-Warnung
(samt JWT-Präfix). Der Bearer-Fallback akzeptiert nun nur noch Token mit dem
API-Key-Präfix "pm_"; ein JWT wird ignoriert. Ergebnis bleibt 401 (Frontend
re-authentifiziert), aber ohne irreführenden Log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 11:12:53 +02:00

73 lines
2.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
CanActivate,
ExecutionContext,
Injectable,
Logger,
UnauthorizedException,
} from '@nestjs/common';
import { ApiKeysService } from './api-keys.service';
@Injectable()
export class ApiKeyGuard implements CanActivate {
private readonly logger = new Logger(ApiKeyGuard.name);
constructor(private readonly apiKeysService: ApiKeysService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const method: string = request.method;
const url: string = request.url;
// Check header (X-API-Key)
let apiKey = request.headers['x-api-key'] || request.headers['X-API-Key'];
let source = 'X-API-Key header';
// Fallback to query parameter (apiKey)
if (!apiKey) {
apiKey = request.query['apiKey'];
if (apiKey) source = 'apiKey query param';
}
// Fallback to Authorization: Bearer (used by SSE clients that can't set
// X-API-Key). Nur akzeptieren, wenn das Token wie ein API-Key aussieht
// (Präfix "pm_"). Ein (abgelaufenes) JWT als Bearer-Token wird hier ignoriert,
// statt es fälschlich als API-Key zu prüfen das vermeidet die irreführende
// "Invalid API Key"-Warnung beim normalen JWT-Ablauf.
if (!apiKey) {
const auth: string | undefined = request.headers['authorization'];
if (auth?.startsWith('Bearer ')) {
const token = auth.slice(7);
if (token.startsWith('pm_')) {
apiKey = token;
source = 'Authorization: Bearer';
}
}
}
this.logger.log(
`[${method} ${url}] key source: ${apiKey ? source : 'NONE'} | ` +
`headers: ${JSON.stringify(Object.keys(request.headers))} | ` +
`key prefix: ${apiKey ? String(apiKey).slice(0, 8) + '…' : 'n/a'}`,
);
if (!apiKey) {
this.logger.warn(`[${method} ${url}] rejected no API key found`);
throw new UnauthorizedException('API Key missing');
}
try {
const keyEntry = await this.apiKeysService.validateKey(apiKey as string);
this.logger.log(
`[${method} ${url}] accepted key "${keyEntry.name}" (id=${keyEntry.id})`,
);
request.apiKeyMetadata = { id: keyEntry.id, name: keyEntry.name };
return true;
} catch (err) {
this.logger.warn(
`[${method} ${url}] rejected validation failed: ${err.message}`,
);
throw new UnauthorizedException(err.message || 'Invalid API Key');
}
}
}