debug(pam): add /api/pam/whoami to inspect session roles

Temporary endpoint to verify what `realm_access.roles` Keycloak puts
into the access token vs. what NextAuth lands in session.user.roles.
Remove once the eligibility plumbing is confirmed working.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Super User
2026-05-18 13:58:00 +02:00
parent af8c4fd0da
commit cb85c1de7a
2 changed files with 50 additions and 1 deletions

View File

@@ -31,7 +31,7 @@ spec:
spec: spec:
containers: containers:
- name: my-ui - name: my-ui
image: registry.gosec.internal/gsc-my/ui:v0.1.1 image: registry.gosec.internal/gsc-my/ui:v0.1.2
imagePullPolicy: Always imagePullPolicy: Always
ports: ports:
- containerPort: 3000 - containerPort: 3000

View File

@@ -0,0 +1,49 @@
// Debug endpoint — returns the session shape as the server sees it.
// Safe to call by the user themselves; reveals nothing about anyone
// else. Remove once eligibility plumbing is verified working.
import { auth } from "@/auth";
export const dynamic = "force-dynamic";
export async function GET() {
const session = await auth();
const u = session?.user as
| {
id?: string;
keycloakId?: string;
tenantId?: string;
customerId?: string;
email?: string;
roles?: string[];
accessToken?: string;
}
| undefined;
if (!u?.id) return Response.json({ error: "unauthorized" }, { status: 401 });
// Re-decode the access token here so we can compare what NextAuth
// wrote into session.user.roles vs. what's currently in realm_access.
let accessTokenRoles: string[] = [];
try {
const payload = u.accessToken?.split(".")[1];
if (payload) {
const padded = payload + "=".repeat((4 - (payload.length % 4)) % 4);
const decoded = Buffer.from(padded, "base64").toString("utf8");
const json = JSON.parse(decoded) as { realm_access?: { roles?: string[] } };
accessTokenRoles = json.realm_access?.roles ?? [];
}
} catch {
/* ignore */
}
return Response.json({
id: u.id,
keycloakId: u.keycloakId,
tenantId: u.tenantId,
customerId: u.customerId,
email: u.email,
sessionRoles: u.roles ?? [],
accessTokenRoles,
eligibleSuffixMatches: (u.roles ?? []).filter((r) => r.endsWith("_eligible")),
});
}