Add i18n factory + AdminShell My Profile + LogoutButton anchor
- src/i18n/server.ts: createI18nConfig factory consolidating the locale resolution chain (cookie → access_token preferred_language claim → Accept-Language → default). Reusable across apps; previously each frontend reimplemented it. - AdminShell: thread signoutPath + myProfileUrl (default https://my.gosec.internal/profile) into the navbar; render My Profile link alongside logout. - LogoutButton: replace two-step fetch+signOut+redirect with a plain anchor pointing at signoutPath — the NextAuth POST-only signout endpoint plus form-CSRF flow doesn't need client JS. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,46 +1,45 @@
|
||||
"use client";
|
||||
|
||||
import { signOut } from "next-auth/react";
|
||||
|
||||
/**
|
||||
* Default flow (shared org Keycloak):
|
||||
* 1. GET /api/auth/logout — host app returns { logoutUrl } pointing at
|
||||
* Keycloak's end_session endpoint (id_token_hint included).
|
||||
* 2. next-auth signOut() locally — fires events.signOut for backchannel
|
||||
* revocation; redirect:false so we control the navigation.
|
||||
* 3. Navigate to logoutUrl — kills the SSO cookie at Keycloak.
|
||||
* Default flow: a plain anchor to `/api/auth/signout` (overridable via
|
||||
* the `signoutPath` prop). The route handler runs RP-initiated logout
|
||||
* in a single redirect — kills the NextAuth cookie, ends the Keycloak
|
||||
* SSO session, lands on the app's signed-out page.
|
||||
*
|
||||
* Apps without /api/auth/logout (or that need a different flow) pass
|
||||
* `onSignOut` to fully replace this behavior.
|
||||
* Apps with a different shape (no `/api/auth/signout`, need to fire
|
||||
* custom telemetry, etc.) pass `onSignOut` to replace the navigation
|
||||
* with a button + custom handler.
|
||||
*/
|
||||
type LogoutButtonProps = {
|
||||
label: string;
|
||||
signoutPath?: string;
|
||||
onSignOut?: () => void | Promise<void>;
|
||||
};
|
||||
|
||||
export function LogoutButton({ label, onSignOut }: LogoutButtonProps) {
|
||||
const handleLogout = async () => {
|
||||
if (onSignOut) {
|
||||
await onSignOut();
|
||||
return;
|
||||
}
|
||||
|
||||
let logoutUrl = "/logged-out";
|
||||
try {
|
||||
const res = await fetch("/api/auth/logout");
|
||||
const body = await res.json();
|
||||
if (body?.logoutUrl) logoutUrl = body.logoutUrl;
|
||||
} catch {
|
||||
// fall through with local-only logout
|
||||
}
|
||||
await signOut({ redirect: false });
|
||||
window.location.href = logoutUrl;
|
||||
};
|
||||
export function LogoutButton({
|
||||
label,
|
||||
signoutPath = "/api/auth/signout",
|
||||
onSignOut,
|
||||
}: LogoutButtonProps) {
|
||||
if (onSignOut) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
void onSignOut();
|
||||
}}
|
||||
className="dropdown-item"
|
||||
>
|
||||
<i className="ph-sign-out me-2"></i>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button type="button" onClick={handleLogout} className="dropdown-item">
|
||||
<a href={signoutPath} className="dropdown-item">
|
||||
<i className="ph-sign-out me-2"></i>
|
||||
{label}
|
||||
</button>
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user