UNPKG

wcz-layout

Version:

237 lines (236 loc) 8.02 kB
import { t as serverEnv$1 } from "./env-CoveIivr.mjs"; import { queryOptions } from "@tanstack/react-query"; import { manifest, permissions, scopes } from "virtual:wcz-layout"; import { redirect } from "@tanstack/react-router"; import { createServerFn, createServerOnlyFn } from "@tanstack/react-start"; //#region src/lib/auth/offline.ts /** * Offline fallback for identity. The refresh token never leaves the sealed * httpOnly cookie — what is cached here is only the display snapshot (user * claims and avatar) so the UI and offline route permissions can still work * while the server is unreachable. Server requests remain independently * authorized. At-rest protection is the device's OS login and disk encryption. */ const OFFLINE_USER_KEY = "wcz-auth-user"; const OFFLINE_PHOTO_KEY = "wcz-auth-photo"; /** One shift — matches Intune's default offline grace period. */ const GRACE_PERIOD = 720 * 60 * 1e3; const recall = (key) => { if (typeof localStorage === "undefined") return null; try { const raw = localStorage.getItem(key); if (!raw) return null; const { value, savedAt } = JSON.parse(raw); if (Date.now() - savedAt > GRACE_PERIOD) { localStorage.removeItem(key); return null; } return value; } catch { localStorage.removeItem(key); return null; } }; const remember = (key, value) => { if (typeof localStorage === "undefined") return; try { if (value === null || value === void 0) localStorage.removeItem(key); else localStorage.setItem(key, JSON.stringify({ value, savedAt: Date.now() })); } catch {} }; const isOfflineFailure = (error) => typeof navigator !== "undefined" && !navigator.onLine || error instanceof TypeError; /** * Answers from the server when reachable, remembering the result; when the call * has a transport failure, replays the last result instead, for up to * {@link GRACE_PERIOD} since it was last confirmed. * * Cached values are untrusted client state. Server authorization must never * depend on them. */ const withOfflineFallback = async (key, fetchFresh) => { try { const fresh = await fetchFresh(); remember(key, fresh); return fresh; } catch (error) { if (!isOfflineFailure(error)) throw error; return recall(key); } }; /** Drops the cached identity. Call on logout, before leaving the page. */ const clearSessionSnapshot = () => { if (typeof localStorage === "undefined") return; localStorage.removeItem(OFFLINE_USER_KEY); localStorage.removeItem(OFFLINE_PHOTO_KEY); localStorage.setItem("wcz-auth-logout", String(Date.now())); }; if (typeof window !== "undefined") window.addEventListener("storage", (event) => { if (event.key === "wcz-auth-logout") location.href = "/auth/logout"; }); //#endregion //#region src/lib/auth/session.ts const getAppSession = async () => { const { useSession: getSession } = await import("@tanstack/react-start/server"); return getSession({ name: "session", password: serverEnv$1.SESSION_SECRET }); }; //#endregion //#region src/lib/auth/user.ts /** * Reads the signed-in user from the session cookie, or null. As a server function * it runs in-process when called on the server (SSR, middleware) and as an RPC * when called from the client — so it doubles as the client `queryFn`. */ const getSessionUser = createServerFn({ method: "GET" }).handler(async () => { return (await getAppSession()).data.user ?? null; }); /** * Client-side query for the signed-in user. Shared between `getUser` (route * `beforeLoad`) and the always-mounted observer in `LayoutProvider` — the * observer is what seeds the offline snapshot on the very first page load * (`beforeLoad` does not re-run after SSR hydration) and what makes * `refetchOnReconnect` fire once connectivity returns. */ const userQueryOptions = queryOptions({ queryKey: ["auth", "user"], queryFn: () => withOfflineFallback(OFFLINE_USER_KEY, () => getSessionUser()), staleTime: Infinity, networkMode: "offlineFirst", refetchOnReconnect: "always" }); const getUser = ({ queryClient }) => { if (import.meta.env.SSR) return getSessionUser(); return queryClient.ensureQueryData(userQueryOptions); }; /** * Server-only token acquisition: a delegated access token for the given API * scope, minted from the user's session refresh token. Entra rotates the refresh * token on each use, so the rotated token is persisted back to the session. Use * inside server functions and middleware — it is stripped from the client bundle * and throws if called there. */ const getAccessToken = createServerOnlyFn(async (scopeKey) => { const session = await getAppSession(); if (!session.data.refreshToken) throw new Error("No active session. User not signed in."); const { acquireDelegatedToken, SessionExpiredError } = await import("./entra-47nBhMK-.mjs"); try { const { accessToken, refreshToken } = await acquireDelegatedToken({ refreshToken: session.data.refreshToken, scopes: scopes[scopeKey] }); if (refreshToken !== session.data.refreshToken) await session.update({ refreshToken }); return accessToken; } catch (error) { if (error instanceof SessionExpiredError) { await session.clear(); throw Response.json({ message: "Unauthorized: Session expired, sign in again" }, { status: 401 }); } throw error; } }); //#endregion //#region src/lib/utils.ts const WISTRON_PRIMARY_COLOR = "#00506E"; const WISTRON_SECONDARY_COLOR = "#64DC00"; var Platform = class { static get isAndroid() { return /android/i.test(this.userAgent); } static get isIOS() { return /iPad|iPhone|iPod/.test(this.userAgent); } static get isWindows() { return /windows/i.test(this.userAgent); } static get isMacOS() { return /Macintosh|MacIntel|MacPPC|Mac68K/.test(this.userAgent); } static get userAgent() { return typeof navigator === "undefined" ? "" : navigator.userAgent; } }; const rootRouteHead = (options) => ({ meta: [ { charSet: "utf-8" }, { name: "viewport", content: "width=device-width, initial-scale=1" }, { title: manifest.name }, { name: "og:type", content: "website" }, { name: "og:title", content: manifest.name }, { name: "og:image", content: "/favicon-32x32.png" } ], links: [ { rel: "apple-touch-icon", sizes: "180x180", href: "/apple-touch-icon.png" }, { rel: "icon", type: "image/png", sizes: "32x32", href: "/favicon-32x32.png" }, { rel: "icon", type: "image/png", sizes: "16x16", href: "/favicon-16x16.png" }, { rel: "manifest", href: options?.manifest || "/manifest.json" }, { rel: "icon", href: "/favicon.ico" } ] }); const requirePermission = (permissionKey) => { return async ({ location, context }) => { const user = await getUser({ queryClient: context.queryClient }); if (!user) throw redirect({ href: `/auth/login?returnTo=${encodeURIComponent(location.href)}`, reloadDocument: true }); if (!hasPermission(user, permissionKey)) throw new Error("You do not have permission to access this page."); return { user }; }; }; const getFieldStatus = (field) => { const { meta } = field.state; return { isTouched: meta.isTouched, hasError: !!meta.errors.length, helperText: meta.errors[0]?.message }; }; const buildUser = (payload) => ({ name: payload.name?.split("/")[0], email: payload.preferred_username?.toLowerCase(), department: payload.department?.toUpperCase() || "", employeeId: payload.employeeId?.toUpperCase() || "", companyName: payload.companyName || "", groups: payload.groups ?? [] }); const hasPermission = (user, key) => user ? permissions[key].some((group) => user.groups.includes(group)) : false; //#endregion export { getFieldStatus as a, rootRouteHead as c, getUser as d, userQueryOptions as f, withOfflineFallback as g, clearSessionSnapshot as h, buildUser as i, getAccessToken as l, OFFLINE_PHOTO_KEY as m, WISTRON_PRIMARY_COLOR as n, hasPermission as o, getAppSession as p, WISTRON_SECONDARY_COLOR as r, requirePermission as s, Platform as t, getSessionUser as u }; //# sourceMappingURL=utils-uByKcwM2.mjs.map