{"version":3,"file":"utils-CnwPC4sC.mjs","names":["serverEnv","getAppSession","useSession","getSession","name","password","SESSION_SECRET","cookie","httpOnly","sameSite","secure","path","createServerFn","createServerOnlyFn","scopes","definedScopes","QueryClientParam","User","getAppSession","getSessionUser","method","handler","Promise","session","data","user","getUser","queryClient","import","meta","env","SSR","ensureQueryData","queryKey","queryFn","staleTime","Infinity","getAccessToken","scopeKey","refreshToken","Error","acquireDelegatedToken","accessToken","update","AnyFieldApi","ParsedLocation","redirect","manifest","permissions","QueryClientParam","TokenPayload","User","getUser","WISTRON_PRIMARY_COLOR","WISTRON_SECONDARY_COLOR","Platform","isAndroid","test","userAgent","isIOS","isWindows","isMacOS","navigator","RootRouteHeadOptions","rootRouteHead","options","meta","charSet","name","content","title","links","rel","sizes","href","type","requirePermission","permissionKey","location","context","user","queryClient","encodeURIComponent","reloadDocument","hasPermission","Error","FormOmittedProps","getFieldStatus","field","state","isTouched","hasError","errors","length","helperText","message","toKebabCase","str","replaceAll","toLowerCase","buildUser","payload","split","email","preferred_username","department","toUpperCase","employeeId","companyName","groups","key","some","group","includes"],"sources":["../src/lib/auth/session.ts","../src/lib/auth/user.ts","../src/lib/utils.ts"],"sourcesContent":["import { serverEnv } from \"~/env\";\r\n\r\nexport const getAppSession = async () => {\r\n  // Dynamic import: `@tanstack/react-start/server` is the server entry and drags\r\n  // in the SSR renderer (`react-dom/server`). Keep it out of the client graph —\r\n  // this helper is only ever called server-side, and all callers already await it.\r\n  // Aliased off the `use*` name so it isn't mistaken for a React hook (it's a\r\n  // server session helper, not a hook).\r\n  const { useSession: getSession } = await import(\"@tanstack/react-start/server\");\r\n\r\n  return getSession({\r\n    name: \"wcz-auth\",\r\n    password: serverEnv.SESSION_SECRET,\r\n    cookie: {\r\n      httpOnly: true,\r\n      sameSite: \"lax\",\r\n      secure: true,\r\n      path: \"/\",\r\n    },\r\n  });\r\n};\r\n","import { createServerFn, createServerOnlyFn } from \"@tanstack/react-start\";\r\nimport { scopes as definedScopes } from \"virtual:wcz-layout\";\r\nimport { QueryClientParam } from \"~/models/QueryClientParam\";\r\nimport type { User } from \"~/models/User\";\r\nimport { getAppSession } from \"./session\";\r\n\r\n/**\r\n * Reads the signed-in user from the session cookie, or null. As a server function\r\n * it runs in-process when called on the server (SSR, middleware) and as an RPC\r\n * when called from the client — so it doubles as the client `queryFn`.\r\n */\r\nexport const getSessionUser = createServerFn({ method: \"GET\" }).handler(\r\n  async (): Promise<User | null> => {\r\n    const session = await getAppSession();\r\n    return session.data.user ?? null;\r\n  },\r\n);\r\n\r\nexport const getUser = ({ queryClient }: QueryClientParam): Promise<User | null> => {\r\n  if (import.meta.env.SSR) return getSessionUser();\r\n\r\n  return queryClient.ensureQueryData({\r\n    queryKey: [\"auth\", \"user\"],\r\n    queryFn: () => getSessionUser(),\r\n    staleTime: Infinity,\r\n  });\r\n};\r\n\r\n/**\r\n * Server-only token acquisition: a delegated access token for the given API\r\n * scope, minted from the user's session refresh token. Entra rotates the refresh\r\n * token on each use, so the rotated token is persisted back to the session. Use\r\n * inside server functions and middleware — it is stripped from the client bundle\r\n * and throws if called there.\r\n */\r\nexport const getAccessToken = createServerOnlyFn(\r\n  async (scopeKey: keyof typeof definedScopes): Promise<string> => {\r\n    const session = await getAppSession();\r\n    if (!session.data.refreshToken) throw new Error(\"No active session. User not signed in.\");\r\n\r\n    // Dynamic import so `entra` (and its `@azure/msal-node` dependency) stays out\r\n    // of the client graph — this module also exports the isomorphic `getUser`.\r\n    const { acquireDelegatedToken } = await import(\"./entra\");\r\n    const { accessToken, refreshToken } = await acquireDelegatedToken({\r\n      refreshToken: session.data.refreshToken,\r\n      scopes: definedScopes[scopeKey],\r\n    });\r\n\r\n    if (refreshToken !== session.data.refreshToken) await session.update({ refreshToken });\r\n    return accessToken;\r\n  },\r\n);\r\n","import type { AnyFieldApi } from \"@tanstack/react-form\";\nimport { ParsedLocation, redirect } from \"@tanstack/react-router\";\nimport { manifest, permissions } from \"virtual:wcz-layout\";\nimport type { QueryClientParam } from \"~/models/QueryClientParam\";\nimport type { TokenPayload } from \"~/models/TokenPayload\";\nimport type { User } from \"~/models/User\";\nimport { getUser } from \"./auth/user\";\n\nexport const WISTRON_PRIMARY_COLOR = \"#00506E\";\nexport const WISTRON_SECONDARY_COLOR = \"#64DC00\";\n\nexport class Platform {\n  static get isAndroid() {\n    return /android/i.test(this.userAgent);\n  }\n  static get isIOS() {\n    return /iPad|iPhone|iPod/.test(this.userAgent);\n  }\n  static get isWindows() {\n    return /windows/i.test(this.userAgent);\n  }\n  static get isMacOS() {\n    return /Macintosh|MacIntel|MacPPC|Mac68K/.test(this.userAgent);\n  }\n\n  private static get userAgent() {\n    return typeof navigator === \"undefined\" ? \"\" : navigator.userAgent;\n  }\n}\n\ninterface RootRouteHeadOptions {\n  manifest?: string;\n}\n\nexport const rootRouteHead = (options?: RootRouteHeadOptions) => ({\n  meta: [\n    { charSet: \"utf-8\" },\n    { name: \"viewport\", content: \"width=device-width, initial-scale=1\" },\n    { title: manifest.name },\n    { name: \"og:type\", content: \"website\" },\n    { name: \"og:title\", content: manifest.name },\n    { name: \"og:image\", content: \"/favicon-32x32.png\" },\n  ],\n  links: [\n    { rel: \"apple-touch-icon\", sizes: \"180x180\", href: \"/apple-touch-icon.png\" },\n    { rel: \"icon\", type: \"image/png\", sizes: \"32x32\", href: \"/favicon-32x32.png\" },\n    { rel: \"icon\", type: \"image/png\", sizes: \"16x16\", href: \"/favicon-16x16.png\" },\n    { rel: \"manifest\", href: options?.manifest || \"/manifest.json\" },\n    { rel: \"icon\", href: \"/favicon.ico\" },\n  ],\n});\n\nexport const requirePermission = (permissionKey: keyof typeof permissions) => {\n  return async ({ location, context }: { location: ParsedLocation; context: QueryClientParam }) => {\n    const user = await getUser({ queryClient: context.queryClient });\n\n    if (!user)\n      throw redirect({\n        href: `/auth/login?returnTo=${encodeURIComponent(location.href)}`,\n        reloadDocument: true,\n      });\n\n    if (!hasPermission(user, permissionKey))\n      throw new Error(\"You do not have permission to access this page.\");\n\n    return { user };\n  };\n};\n\n/* Internal Utils */\nexport type FormOmittedProps =\n  | \"name\"\n  | \"value\"\n  | \"onChange\"\n  | \"onBlur\"\n  | \"error\"\n  | \"helperText\"\n  | \"renderInput\"\n  | \"type\"\n  | \"aria-label\";\n\nexport const getFieldStatus = (field: AnyFieldApi) => {\n  const { meta } = field.state;\n\n  const isTouched = meta.isTouched;\n  const hasError = !!meta.errors.length;\n  const helperText = meta.errors[0]?.message;\n\n  return { isTouched, hasError, helperText };\n};\n\nexport const toKebabCase = (str: string): string => {\n  return str\n    .replaceAll(/([a-z])([A-Z])/g, \"$1-$2\")\n    .replaceAll(/[\\s_]+/g, \"-\")\n    .replaceAll(/[^a-zA-Z0-9-]/g, \"\")\n    .toLowerCase()\n    .replaceAll(/-+/g, \"-\")\n    .replaceAll(/(^-|-$)/g, \"\");\n};\n\nexport const buildUser = (payload: TokenPayload): User => ({\n  name: payload.name?.split(\"/\")[0],\n  email: payload.preferred_username?.toLowerCase(),\n  department: payload.department?.toUpperCase() || \"\",\n  employeeId: payload.employeeId?.toUpperCase() || \"\",\n  companyName: payload.companyName || \"\",\n  groups: payload.groups ?? [],\n});\n\nexport const hasPermission = (user: User | null, key: keyof typeof permissions): boolean =>\n  user ? permissions[key].some((group) => user.groups.includes(group)) : false;\n"],"mappings":"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"}