chore: apply ESLint auto-fix across entire backend
Build and Push Multi-Platform Images / build-and-push (push) Successful in 41s
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>
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
import { CanActivate, ExecutionContext, Injectable, Logger, UnauthorizedException } from '@nestjs/common';
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
Logger,
|
||||
UnauthorizedException,
|
||||
} from '@nestjs/common';
|
||||
import { ApiKeysService } from './api-keys.service';
|
||||
|
||||
@Injectable()
|
||||
@@ -33,8 +39,8 @@ export class ApiKeyGuard implements CanActivate {
|
||||
|
||||
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'}`,
|
||||
`headers: ${JSON.stringify(Object.keys(request.headers))} | ` +
|
||||
`key prefix: ${apiKey ? String(apiKey).slice(0, 8) + '…' : 'n/a'}`,
|
||||
);
|
||||
|
||||
if (!apiKey) {
|
||||
@@ -44,11 +50,15 @@ export class ApiKeyGuard implements CanActivate {
|
||||
|
||||
try {
|
||||
const keyEntry = await this.apiKeysService.validateKey(apiKey as string);
|
||||
this.logger.log(`[${method} ${url}] accepted – key "${keyEntry.name}" (id=${keyEntry.id})`);
|
||||
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}`);
|
||||
this.logger.warn(
|
||||
`[${method} ${url}] rejected – validation failed: ${err.message}`,
|
||||
);
|
||||
throw new UnauthorizedException(err.message || 'Invalid API Key');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Controller, Get, Post, Delete, Body, Param, UseGuards } from '@nestjs/common';
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Post,
|
||||
Delete,
|
||||
Body,
|
||||
Param,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import { ApiKeysService } from './api-keys.service';
|
||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
||||
|
||||
|
||||
@@ -13,22 +13,27 @@ export class ApiKeysService {
|
||||
private readonly apiKeyRepo: Repository<ApiKey>,
|
||||
) {}
|
||||
|
||||
async createApiKey(name: string, expiresDays?: number): Promise<{ plainKey: string; entity: ApiKey }> {
|
||||
async createApiKey(
|
||||
name: string,
|
||||
expiresDays?: number,
|
||||
): Promise<{ plainKey: string; entity: ApiKey }> {
|
||||
const prefix = 'pm_';
|
||||
const randomPart = crypto.randomBytes(24).toString('hex'); // 48 chars hex
|
||||
const plainKey = `${prefix}${randomPart}`;
|
||||
|
||||
|
||||
const keyHash = this.hashKey(plainKey);
|
||||
|
||||
|
||||
const apiKey = this.apiKeyRepo.create({
|
||||
name,
|
||||
keyPrefix: prefix,
|
||||
keyHash,
|
||||
expiresAt: expiresDays ? new Date(Date.now() + expiresDays * 24 * 60 * 60 * 1000) : null,
|
||||
expiresAt: expiresDays
|
||||
? new Date(Date.now() + expiresDays * 24 * 60 * 60 * 1000)
|
||||
: null,
|
||||
});
|
||||
|
||||
const savedKey = await this.apiKeyRepo.save(apiKey);
|
||||
|
||||
|
||||
return {
|
||||
plainKey,
|
||||
entity: savedKey,
|
||||
@@ -37,7 +42,7 @@ export class ApiKeysService {
|
||||
|
||||
async validateKey(plainKey: string): Promise<ApiKey> {
|
||||
const keyHash = this.hashKey(plainKey);
|
||||
|
||||
|
||||
const apiKey = await this.apiKeyRepo.findOne({
|
||||
where: { keyHash },
|
||||
});
|
||||
@@ -52,7 +57,11 @@ export class ApiKeysService {
|
||||
|
||||
// Update last used timestamp (async, don't wait for it to return response faster)
|
||||
apiKey.lastUsedAt = new Date();
|
||||
this.apiKeyRepo.save(apiKey).catch(err => this.logger.error('Fehler beim Aktualisieren von lastUsedAt', err));
|
||||
this.apiKeyRepo
|
||||
.save(apiKey)
|
||||
.catch((err) =>
|
||||
this.logger.error('Fehler beim Aktualisieren von lastUsedAt', err),
|
||||
);
|
||||
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,14 @@ import { PermissionsGuard } from './permissions.guard';
|
||||
useClass: PermissionsGuard,
|
||||
},
|
||||
],
|
||||
exports: [PassportModule, ApiKeysService, ApiKeyGuard, JwtAuthGuard, JwtOrApiKeyGuard, PermissionsGuard, TypeOrmModule],
|
||||
exports: [
|
||||
PassportModule,
|
||||
ApiKeysService,
|
||||
ApiKeyGuard,
|
||||
JwtAuthGuard,
|
||||
JwtOrApiKeyGuard,
|
||||
PermissionsGuard,
|
||||
TypeOrmModule,
|
||||
],
|
||||
})
|
||||
export class AuthModule {}
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { CanActivate, ExecutionContext, Injectable, Logger } from '@nestjs/common';
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
Logger,
|
||||
} from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { JwtAuthGuard } from './jwt-auth.guard';
|
||||
import { ApiKeyGuard } from './api-key.guard';
|
||||
@@ -28,7 +33,9 @@ export class JwtOrApiKeyGuard implements CanActivate {
|
||||
// Try JWT first
|
||||
try {
|
||||
const result = this.jwtGuard.canActivate(context);
|
||||
const jwtOk = isObservable(result) ? await lastValueFrom(result) : await result;
|
||||
const jwtOk = isObservable(result)
|
||||
? await lastValueFrom(result)
|
||||
: await result;
|
||||
if (jwtOk) {
|
||||
this.logger.log(`${tag} authenticated via JWT`);
|
||||
return true;
|
||||
|
||||
@@ -24,7 +24,14 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
|
||||
});
|
||||
}
|
||||
|
||||
validate(payload: any): { userId: string; email: string; name: string; preferredUsername: string | null; groups: string[]; permissions: any[] } {
|
||||
validate(payload: any): {
|
||||
userId: string;
|
||||
email: string;
|
||||
name: string;
|
||||
preferredUsername: string | null;
|
||||
groups: string[];
|
||||
permissions: any[];
|
||||
} {
|
||||
const groups = payload.groups || [];
|
||||
return {
|
||||
userId: payload.sub,
|
||||
|
||||
@@ -2,4 +2,5 @@ import { SetMetadata } from '@nestjs/common';
|
||||
import { Permission } from './permissions.enum';
|
||||
|
||||
export const PERMISSIONS_KEY = 'permissions';
|
||||
export const RequirePermissions = (...permissions: Permission[]) => SetMetadata(PERMISSIONS_KEY, permissions);
|
||||
export const RequirePermissions = (...permissions: Permission[]) =>
|
||||
SetMetadata(PERMISSIONS_KEY, permissions);
|
||||
|
||||
@@ -8,9 +8,11 @@ export const Permission = {
|
||||
VIEW_FREIGABE: 'VIEW_FREIGABE',
|
||||
} as const;
|
||||
|
||||
export type Permission = typeof Permission[keyof typeof Permission];
|
||||
export type Permission = (typeof Permission)[keyof typeof Permission];
|
||||
|
||||
export function mapGroupsToPermissions(groups: string[] | undefined | null): Permission[] {
|
||||
export function mapGroupsToPermissions(
|
||||
groups: string[] | undefined | null,
|
||||
): Permission[] {
|
||||
const permissions = new Set<Permission>();
|
||||
|
||||
if (!groups || !Array.isArray(groups)) {
|
||||
@@ -28,7 +30,8 @@ export function mapGroupsToPermissions(groups: string[] | undefined | null): Per
|
||||
return Array.from(permissions);
|
||||
}
|
||||
|
||||
if (groups.includes('PM_Belege')) permissions.add(Permission.PROCESS_MANUALLY);
|
||||
if (groups.includes('PM_Belege'))
|
||||
permissions.add(Permission.PROCESS_MANUALLY);
|
||||
if (groups.includes('PM_Maileingang')) permissions.add(Permission.VIEW_MAIL);
|
||||
if (groups.includes('PM_Posteingang')) permissions.add(Permission.VIEW_INBOX);
|
||||
if (groups.includes('PM_Scanner')) permissions.add(Permission.VIEW_SCANNER);
|
||||
|
||||
@@ -8,32 +8,34 @@ export class PermissionsGuard implements CanActivate {
|
||||
constructor(private reflector: Reflector) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const requiredPermissions = this.reflector.getAllAndOverride<Permission[]>(PERMISSIONS_KEY, [
|
||||
context.getHandler(),
|
||||
context.getClass(),
|
||||
]);
|
||||
|
||||
const requiredPermissions = this.reflector.getAllAndOverride<Permission[]>(
|
||||
PERMISSIONS_KEY,
|
||||
[context.getHandler(), context.getClass()],
|
||||
);
|
||||
|
||||
if (!requiredPermissions) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const { user } = request;
|
||||
|
||||
if (request.apiKeyMetadata) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (!user || !user.permissions) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const userPermissions = user.permissions as Permission[];
|
||||
|
||||
|
||||
if (userPermissions.includes(Permission.MANAGE_ALL)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return requiredPermissions.some((permission) => userPermissions.includes(permission));
|
||||
|
||||
return requiredPermissions.some((permission) =>
|
||||
userPermissions.includes(permission),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user