Files
sarlinkportal-webui/src/lib/auth-store.ts
T
shihaam d2fd218ee4
Build and Push Docker Images / Build and Push Docker Images (push) Failing after 9s
fix admin user related issues
2026-08-03 21:44:57 +05:00

80 lines
2.9 KiB
TypeScript

import { atomWithStorage } from "jotai/utils";
import { store } from "./atoms";
import type { AuthUser } from "./types/user";
/**
* Client-side auth store (replaces the old next-auth session).
*
* The Knox token + user returned by `POST /callback/auth/` are persisted to
* localStorage via `atomWithStorage`, so they survive reloads and stay reactive
* for components (`userAtom` drives the header wallet balance, welcome banner,
* and sidebar admin/permission gating).
*
* Both the reactive atoms and the non-React helpers below operate on the SAME
* shared jotai `store` (from ./atoms, which the app <Provider> is bound to), so
* a write from the axios interceptor (e.g. `clearAuth()` on 401) is immediately
* visible to React components — no split-brain between localStorage and state.
*/
export const TOKEN_KEY = "sarlink_token";
export const USER_KEY = "sarlink_user";
// `getOnInit: true` is REQUIRED here. Without it, jotai defers reading
// localStorage until the atom is mounted by a React component, so the
// synchronous `store.get()` calls in `getToken()` / `isAuthenticated()`
// (used by RouteGuard, RootRedirect, and the axios interceptor during the
// first render on a page refresh) would see the initial `null` instead of the
// persisted token — bouncing an already-authenticated user to signin on reload.
export const tokenAtom = atomWithStorage<string | null>(TOKEN_KEY, null, undefined, {
getOnInit: true,
});
export const userAtom = atomWithStorage<AuthUser | null>(USER_KEY, null, undefined, {
getOnInit: true,
});
/** Read the raw token (used by the axios request interceptor). */
export function getToken(): string | null {
return store.get(tokenAtom);
}
/** Read the stored user without a React hook. */
export function getStoredUser(): AuthUser | null {
return store.get(userAtom);
}
/**
* Single source of truth for "is this user an administrator?" on the client.
* Mirrors the backend `user_is_admin` helper: any of the app-specific
* `is_admin` flag, Django `is_staff`, or `is_superuser` grants admin access.
* Use this for every admin route/UI gate instead of checking `is_admin` alone.
*/
export function isAdminUser(user: AuthUser | null | undefined): boolean {
return Boolean(user && (user.is_admin || user.is_staff || user.is_superuser));
}
/** Persist token + user on successful login. */
export function setAuth(token: string, user: AuthUser): void {
store.set(tokenAtom, token);
store.set(userAtom, user);
}
/** Clear token + user on logout / 401. */
export function clearAuth(): void {
store.set(tokenAtom, null);
store.set(userAtom, null);
}
/**
* True when a token is present and, if we know its expiry, it hasn't passed.
* Used by RouteGuard and the root redirect.
*/
export function isAuthenticated(): boolean {
const token = getToken();
if (!token) return false;
const user = getStoredUser();
if (user?.expiry) {
return new Date(user.expiry).getTime() > Date.now();
}
return true;
}