dad0136365
Build and Push Multi-Platform Images / build-and-push (push) Successful in 41s
Reformats code style (line breaks, indentation, type annotations) without changing logic. Also includes minor feature additions bundled in the same lint run (stats service, user-settings groups, agrarmonitor polling improvements). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
66 lines
2.1 KiB
TypeScript
66 lines
2.1 KiB
TypeScript
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)
|
||
if (!apiKey) {
|
||
const auth: string | undefined = request.headers['authorization'];
|
||
if (auth?.startsWith('Bearer ')) {
|
||
apiKey = auth.slice(7);
|
||
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');
|
||
}
|
||
}
|
||
}
|