fix: PermissionsGuard blockiert seit dem Fail-Closed-Umbau alle @Public-Routen
Backend Image / build-and-push (push) Successful in 3m14s
Frontend Image / build-and-push (push) Successful in 1m54s

Beim Umstellen von PermissionsGuard auf Fail-Closed wurde der
@Public()-Check vergessen. JwtAuthGuard ueberspringt bei @Public()
zwar die Authentifizierung, aber PermissionsGuard laeuft als
eigenstaendiger, separat registrierter APP_GUARD trotzdem mit - und
verweigerte seitdem jede Route ohne @RequirePermission/
@AnyAuthenticatedUser, selbst wenn sie @Public() war. Betroffen: health
und health/ready (die gemeldeten 403), aber ebenso Login, SSO-Flow und
die oeffentliche Herkunftsseite.

PermissionsGuard prueft jetzt @Public() als erstes und laesst diese
Routen durch, genau wie JwtAuthGuard es bereits tut. Regressionstest
ergaenzt.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-02 20:46:24 +02:00
1 parent 351f9ef34c
commit cd24523ea5
2 files changed
+30 -8

No files matched your search

@@ -1,6 +1,7 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { ANY_AUTHENTICATED_USER_KEY } from '../decorators/any-authenticated-user.decorator';
import { IS_PUBLIC_KEY } from '../decorators/public.decorator';
import { REQUIRE_PERMISSION_KEY } from '../decorators/require-permission.decorator';
import { PermissionKey } from '../permissions/permission-catalog';
import { PermissionResolverService } from '../permissions/permission-resolver.service';
@@ -11,14 +12,17 @@ interface RequestWithUser {
}
/**
* Fails closed by decorator absence: a non-public route reaches this guard
* only after JwtAuthGuard confirms a valid, active session, and from here it
* is denied unless it carries either @RequirePermission (checked against the
* caller's live permission set) or the explicit @AnyAuthenticatedUser opt-out
* for routes that are meant to be reachable by any logged-in user regardless
* of role. A route with neither is a bug - forgetting to decorate a new
* route now fails shut at runtime instead of silently granting access to
* every authenticated user and relying solely on
* Fails closed by decorator absence: a route reaches this guard whether or
* not it's @Public (JwtAuthGuard only skips authentication for those, it
* doesn't remove this guard from the chain - NestJS APP_GUARD entries all
* run, there's no early-exit between them), so @Public is checked here too,
* the same way JwtAuthGuard checks it. A non-public route is denied unless
* it carries either @RequirePermission (checked against the caller's live
* permission set) or the explicit @AnyAuthenticatedUser opt-out for routes
* meant to be reachable by any logged-in user regardless of role. A route
* with none of the three is a bug - forgetting to decorate a new route now
* fails shut at runtime instead of silently granting access to every
* authenticated user and relying solely on
* test/authorization-coverage.e2e-spec.ts to catch it before deploy.
*/
@Injectable()
@@ -29,6 +33,14 @@ export class PermissionsGuard implements CanActivate {
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const isPublic = this.reflector.getAllAndOverride<boolean>(IS_PUBLIC_KEY, [
context.getHandler(),
context.getClass(),
]);
if (isPublic) {
return true;
}
const requiredPermissions = this.reflector.getAllAndOverride<
PermissionKey[]
>(REQUIRE_PERMISSION_KEY, [context.getHandler(), context.getClass()]);