wcz-layout
Version:
237 lines (236 loc) • 8.02 kB
JavaScript
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