Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 1x 11x 11x 6x 6x 11x 11x 6x 6x 6x 5x 5x 5x 5x 5x 5x 5x 5x 5x 5x 6x 6x 11x 3x 7x 1x 1x 3x | "use client";
import { useSession } from "next-auth/react";
import { AlertTriangle, ExternalLink, X } from "lucide-react";
import { useEffect, useState } from "react";
const IS_STATIC = process.env.NEXT_PUBLIC_STATIC_EXPORT === "true";
/**
* Dismissible warning banner shown to authenticated users reminding them
* to change the default demo password. Links to Keycloak account security.
*
* - Hidden in static export mode (no real auth)
* - Dismissed state stored in sessionStorage (reappears next browser session)
* - Keycloak URL fetched at runtime from /api/keycloak-config so the link
* points at the correct host on Azure (NEXT_PUBLIC_* is baked at build
* time and would leak "localhost:8080" into production bundles).
*/
export default function DemoPasswordBanner() {
const { data: session, status } = useSession();
const [dismissed, setDismissed] = useState(() => {
Iif (typeof window === "undefined") {
return false;
}
return sessionStorage.getItem("demo-password-banner-dismissed") === "true";
});
const [passwordUrl, setPasswordUrl] = useState<string | null>(null);
useEffect(() => {
Iif (IS_STATIC) return;
let cancelled = false;
fetch("/api/keycloak-config", { cache: "no-store" })
.then((r) => (r.ok ? r.json() : null))
.then((cfg) => {
Iif (cancelled || !cfg?.publicUrl || !cfg?.clientId) return;
// Use Keycloak's OIDC "required action" flow (`kc_action`) rather
// than the Account Console — the latter crashes with "Something
// went wrong" when the realm lacks specific account-console client
// config, which our Azure realm hits today. The UPDATE_PASSWORD
// action is canonical Keycloak, works on every realm, and returns
// the user to our origin after the change.
const redirect = encodeURIComponent(window.location.origin);
const url = new URL(`${cfg.publicUrl}/protocol/openid-connect/auth`);
url.searchParams.set("client_id", cfg.clientId);
url.searchParams.set("redirect_uri", decodeURIComponent(redirect));
url.searchParams.set("response_type", "code");
url.searchParams.set("scope", "openid");
url.searchParams.set("kc_action", "UPDATE_PASSWORD");
setPasswordUrl(url.toString());
})
.catch(() => {
/* leave null — link just hides until config resolves */
});
return () => {
cancelled = true;
};
}, []);
if (IS_STATIC || status !== "authenticated" || !session || dismissed) {
return null;
}
if (!passwordUrl) return null;
function handleDismiss() {
sessionStorage.setItem("demo-password-banner-dismissed", "true");
setDismissed(true);
}
return (
<div
role="alert"
className="bg-amber-50 dark:bg-amber-900/40 border-b border-amber-200 dark:border-amber-700/50 px-4 py-2 flex items-center gap-3 text-sm"
>
<AlertTriangle
size={16}
className="text-[var(--warning-text)] shrink-0"
/>
<p className="text-[var(--warning-text)] flex-1">
<span className="font-semibold">Demo mode:</span> You are using a
default password.{" "}
<a
href={passwordUrl}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-[var(--warning-text)] underline underline-offset-2 hover:opacity-80 transition-opacity"
>
Change your password
<ExternalLink size={12} />
</a>
</p>
<button
onClick={handleDismiss}
className="text-[var(--warning-text)] hover:bg-amber-100 dark:hover:bg-amber-800/40 transition-colors p-1 rounded"
aria-label="Dismiss password warning"
>
<X size={16} />
</button>
</div>
);
}
|