mirror of
https://github.com/luxfi/mpc.git
synced 2026-09-24 00:26:24 +00:00
The dashboard had its own header, its own user menu (components/layout/user-menu.tsx) and its own branding table (lib/branding.ts) that mapped a hostname to a brand a second time — a copy of what @luxfi/ui/white-label already answers, and one that drifted (it had no bootno.de row and pointed zoo at the wrong issuer). Both are deleted. The nav is now `AppNav` from @luxfi/ui: brand, org switcher, links, settings and the user menu, with the links collapsing into the same gui Sheet the switchers use instead of the old `hidden md:flex` that simply vanished on a phone with nothing to replace it. Identity comes from IdentityProvider (IAM: get-account / get-organizations / get-organization-projects). Credentials stay with @hanzo/iam — one PKCE flow and one token store per surface; the chrome borrows only signIn and signOut. `next build` passes: 60 routes.
73 lines
2.6 KiB
TypeScript
73 lines
2.6 KiB
TypeScript
// The MPC API's own JWT, and the server-visible session flag.
|
|
//
|
|
// TWO tokens, two audiences, said plainly:
|
|
// * the IAM access token — the SESSION. Owned by @hanzo/iam (PKCE, one
|
|
// refresh timer) under its own `hanzo_iam_*` keys, and read by the shared
|
|
// @luxfi/ui chrome. Nothing in this file touches it.
|
|
// * the MPC API JWT below — minted FROM the IAM token by `POST /auth/oidc`,
|
|
// and only good against the MPC API.
|
|
//
|
|
// `markSession` is the one bit Next middleware can see: sessionStorage is
|
|
// invisible to the server, so the login writes a flag cookie the matcher reads.
|
|
// It is set by the IAM login succeeding — NOT by the MPC exchange, which is a
|
|
// downstream call that may legitimately fail while the user is signed in.
|
|
|
|
const ACCESS_TOKEN_KEY = 'lux_mpc_access_token'
|
|
const REFRESH_TOKEN_KEY = 'lux_mpc_refresh_token'
|
|
const USER_EMAIL_KEY = 'lux_mpc_user_email'
|
|
|
|
export const SESSION_COOKIE = 'lux_mpc_session'
|
|
|
|
export function getToken(): string | null {
|
|
if (typeof window === 'undefined') return null
|
|
return localStorage.getItem(ACCESS_TOKEN_KEY)
|
|
}
|
|
|
|
export function getRefreshToken(): string | null {
|
|
if (typeof window === 'undefined') return null
|
|
return localStorage.getItem(REFRESH_TOKEN_KEY)
|
|
}
|
|
|
|
export function setTokens(accessToken: string, refreshToken: string): void {
|
|
if (typeof window === 'undefined') return
|
|
localStorage.setItem(ACCESS_TOKEN_KEY, accessToken)
|
|
localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken)
|
|
}
|
|
|
|
/**
|
|
* Raise or clear the flag the middleware gates on.
|
|
*
|
|
* `Secure` is added only on https: an unconditional `Secure` meant the cookie
|
|
* was silently dropped on http://localhost, so every local sign-in bounced
|
|
* straight back to /login.
|
|
*/
|
|
export function markSession(on: boolean): void {
|
|
if (typeof window === 'undefined') return
|
|
const secure = window.location.protocol === 'https:' ? '; Secure' : ''
|
|
document.cookie = on
|
|
? `${SESSION_COOKIE}=1; path=/; SameSite=Lax${secure}`
|
|
: `${SESSION_COOKIE}=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Lax${secure}`
|
|
}
|
|
|
|
export function setUserEmail(email: string): void {
|
|
if (typeof window === 'undefined') return
|
|
localStorage.setItem(USER_EMAIL_KEY, email)
|
|
}
|
|
|
|
export function getUserEmail(): string | null {
|
|
if (typeof window === 'undefined') return null
|
|
return localStorage.getItem(USER_EMAIL_KEY)
|
|
}
|
|
|
|
export function clearTokens(): void {
|
|
if (typeof window === 'undefined') return
|
|
localStorage.removeItem(ACCESS_TOKEN_KEY)
|
|
localStorage.removeItem(REFRESH_TOKEN_KEY)
|
|
localStorage.removeItem(USER_EMAIL_KEY)
|
|
markSession(false)
|
|
}
|
|
|
|
export function isAuthenticated(): boolean {
|
|
return getToken() !== null
|
|
}
|