Spaces:
Running
Running
deploy(hf): sync szl-holdings/nexus@a0cd605f4d1021510a8e351de4016b2453e091e5 derived COPY set
8441695 verified Download src/lib/auth/server.ts from SZLHOLDINGS/nexus: direct link, hf CLI and curl.
- Browser
- Download file 13.5 kB
-
https://huggingface.co/spaces/SZLHOLDINGS/nexus/resolve/main/src/lib/auth/server.ts
- Command line
-
hf download hf://spaces/SZLHOLDINGS/nexus/src/lib/auth/server.ts
-
curl -L -o server.ts https://huggingface.co/spaces/SZLHOLDINGS/nexus/resolve/main/src/lib/auth/server.ts
13.5 kB
| /** | |
| * Self-hosted Better Auth for THIS app (server-only). | |
| * | |
| * Pre-wired for live preview + deploy β do not rewrite this file. To enable | |
| * local email/password, flip the flag in `./email-password` only (see auth skill). | |
| * | |
| * The app runs its own Better Auth at `/api/auth/*`, so the session cookie stays | |
| * on this app's own origin. Sign-in federates to the shared **Grok auth broker** | |
| * (`GROK_AUTH_ISSUER`) via the `genericOAuth` plugin β the broker brokers the | |
| * upstream sign-in methods (Google, X, β¦) and holds their shared secrets; this | |
| * app only holds its own client id/secret and names the upstream it wants via | |
| * each provider's `idp` hint. | |
| * | |
| * Modes (decided in `./federation`): | |
| * - Deployed: the deployer injects a per-app `GROK_AUTH_*` + `BETTER_AUTH_URL` | |
| * + `DATABASE_URL`, so real federated auth is persisted in Postgres. | |
| * - Refused (fail closed): `BETTER_AUTH_URL` is set but the per-app | |
| * `GROK_AUTH_CLIENT_ID` / `GROK_AUTH_CLIENT_SECRET` pair is not. Federated | |
| * sign-in is unavailable and logged as an error; the shared preview client | |
| * is never substituted, and `requireUserId` refuses instead of resolving | |
| * the dev user (see `verify.server.ts`). | |
| * - Sandbox live preview: no `BETTER_AUTH_URL` -> the shared **preview | |
| * client** (`./preview`) and the preview's `https://*.grok-sandbox.com` | |
| * origin derived from the request, so real sign-in works (no demo users). | |
| * Sessions and identities persist in the embedded PGLite DB (same DB as app data); | |
| * the process restart wipes both. Live-preview iframe clients use a bearer | |
| * token (partitioned cookies) β see `client.ts`. | |
| * - Off (`VITE_AUTH_ENABLED=false`, the shipped default): no providers; | |
| * `requireUserId` resolves a dev user with no database configured, and | |
| * throws fail-closed once `DATABASE_URL` is set (see `verify.server.ts`). | |
| * | |
| * NEVER import this from client code β it pulls in `pg` + the preview secret + | |
| * server-only Better Auth internals. The client uses `@/lib/auth/client`; | |
| * components read the user via `@/lib/auth/use-current-user`; server functions get | |
| * a verified id via `@/lib/auth/middleware`. | |
| */ | |
| import { betterAuth } from "better-auth"; | |
| import { bearer, genericOAuth } from "better-auth/plugins"; | |
| import { tanstackStartCookies } from "better-auth/tanstack-start"; | |
| import { getCookie } from "@tanstack/react-start/server"; | |
| import { randomBytes } from "node:crypto"; | |
| import { Pool } from "pg"; | |
| import { ensureDbReady, getPglite } from "../db"; | |
| import { emailAndPasswordEnabled } from "./email-password"; | |
| import { GATE_PROVIDER_ID, gateIdentitySessions } from "./gate-session.server"; | |
| import { GROK_PROVIDERS } from "./providers"; | |
| import { pgliteDialect } from "./pglite-dialect"; | |
| import { resolveFederation, type FederationMode } from "./federation"; | |
| import { | |
| GROK_ISSUER_DEFAULT, | |
| PREVIEW_ALLOWED_HOSTS, | |
| PREVIEW_CLIENT_ID, | |
| PREVIEW_CLIENT_SECRET, | |
| } from "./preview"; | |
| // Kick (and share) PGLite bootstrap as soon as the auth server module loads. | |
| void ensureDbReady(); | |
| /** | |
| * Preview secret must outlive module reloads: PGLite (and its session rows) is | |
| * stored on `globalThis`, so an HMR re-eval of this file must NOT mint a new | |
| * signing secret or every existing session becomes invalid mid-dev. Process | |
| * restart clears both the secret and PGLite together. | |
| */ | |
| const globalAuthRef = globalThis as typeof globalThis & { | |
| __grokAuthPreviewSecret__?: string; | |
| }; | |
| function previewAuthSecret(): string { | |
| globalAuthRef.__grokAuthPreviewSecret__ ??= randomBytes(32).toString("hex"); | |
| return globalAuthRef.__grokAuthPreviewSecret__; | |
| } | |
| /** Read an env var, treating empty/whitespace as unset. */ | |
| const env = (key: string): string | undefined => { | |
| const value = process.env[key]?.trim(); | |
| return value ? value : undefined; | |
| }; | |
| // Broker federation creds. `VITE_AUTH_ENABLED=false` forces auth off everywhere | |
| // (dev user). When deployed (`BETTER_AUTH_URL` set) the deployer must inject a | |
| // per-app client; without both values federation is REFUSED, never backfilled | |
| // with the shared preview client. Only the dynamic sandbox preview (no | |
| // `BETTER_AUTH_URL`) may use the preview client, which the broker accepts for | |
| // any `*.grok-sandbox.com` callback (see `./preview` and `./federation`). | |
| const federation = resolveFederation(env, { | |
| clientId: PREVIEW_CLIENT_ID, | |
| clientSecret: PREVIEW_CLIENT_SECRET, | |
| }); | |
| const grokIssuer = env("GROK_AUTH_ISSUER") ?? GROK_ISSUER_DEFAULT; | |
| const grokClient = | |
| federation.mode === "preview" || federation.mode === "deployed" ? federation : null; | |
| /** Which federation mode this server resolved (see `./federation`). */ | |
| export const authMode: FederationMode = federation.mode; | |
| /** True when federated sign-in is active (real auth is enforced). */ | |
| export const authConfigured = grokClient !== null; | |
| /** | |
| * Why federated sign-in was refused (fail closed), or null. Names environment | |
| * variables only, never values. | |
| */ | |
| export const authRefusal: string | null = | |
| federation.mode === "refused" ? federation.reason : null; | |
| if (authRefusal !== null) console.error(`[auth] ${authRefusal}`); | |
| // This app's own Better Auth origin. When deployed the deployer injects the | |
| // public URL. In the sandbox live preview there's no fixed URL (each preview gets | |
| // a dynamic `*.grok-sandbox.com` host), so we hand Better Auth a dynamic baseURL: | |
| // it derives the origin per-request from the (proxied) host, validated against the | |
| // preview allowlist, which makes the OAuth `redirect_uri` the concrete preview URL | |
| // the broker's preview client accepts. | |
| const explicitBaseURL = env("BETTER_AUTH_URL"); | |
| // Explicit `string[]` (not a readonly tuple) β Better Auth's DynamicBaseURLConfig | |
| // requires a mutable `allowedHosts: string[]`. | |
| const previewAllowedHosts: string[] = [...PREVIEW_ALLOWED_HOSTS]; | |
| // Local `npm run dev` (port 8080 contract). Browsers may send Origin as any of | |
| // these for the same server β trusting only `localhost` rejects `127.0.0.1` and | |
| // breaks email/password with "Invalid origin". | |
| const LOCAL_DEV_ORIGINS: string[] = [ | |
| "http://localhost:8080", | |
| "http://127.0.0.1:8080", | |
| "http://[::1]:8080", | |
| ]; | |
| const baseURL = explicitBaseURL ?? { | |
| // Include loopback hosts so dynamic baseURL resolves for local email/password | |
| // (not only the preview wildcard). | |
| allowedHosts: [...previewAllowedHosts, "localhost", "127.0.0.1", "[::1]"], | |
| // `auto` β trust both http:// and https:// expansions of allowedHosts | |
| // (preview is https; local dev is http). | |
| protocol: "auto" as const, | |
| fallback: "http://localhost:8080", | |
| }; | |
| // Origins Better Auth accepts on credentialed POSTs (sign-up/sign-in, etc.). | |
| // Missing entries here surface as FORBIDDEN "Invalid origin". | |
| const trustedOrigins: string[] = explicitBaseURL | |
| ? [explicitBaseURL, ...LOCAL_DEV_ORIGINS] | |
| : [ | |
| // Host wildcards (matched against Origin's host) | |
| ...previewAllowedHosts, | |
| // Full-origin wildcards (matched against Origin) | |
| ...previewAllowedHosts.flatMap((host) => [`https://${host}`, `http://${host}`]), | |
| ...LOCAL_DEV_ORIGINS, | |
| ]; | |
| const databaseUrl = env("DATABASE_URL"); | |
| // Static broker OAuth endpoints (skip OIDC discovery on every sign-in / callback). | |
| // Discovery would cost an extra network hop to the broker before the popup can | |
| // even redirect to Google/X β the live-preview popup felt stuck on the app for | |
| // that whole round-trip. These paths match the broker's discovery document. | |
| const issuerBase = grokIssuer.replace(/\/+$/, ""); | |
| const grokAuthorizationUrl = `${issuerBase}/api/auth/oauth2/authorize`; | |
| const grokTokenUrl = `${issuerBase}/api/auth/oauth2/token`; | |
| const grokUserInfoUrl = `${issuerBase}/api/auth/oauth2/userinfo`; | |
| // Real Postgres when `DATABASE_URL` is set (deployed apps), else the app's | |
| // embedded PGLite (preview) via a Kysely dialect β so Better Auth persists to the | |
| // SAME DB as app data, including email/password users. Both use the Better Auth | |
| // schema from `migrations/auth/0001_auth.sql`, copied into `migrations/` when | |
| // the app turns sign-in on. | |
| const database = databaseUrl | |
| ? new Pool({ connectionString: databaseUrl }) | |
| : { dialect: pgliteDialect(() => getPglite()), type: "postgres" as const }; | |
| /** Session token cookie name β also read by the live-preview popup completion page. */ | |
| export const SESSION_TOKEN_COOKIE = "__Host-grok-auth.session_token"; | |
| // Built separately so the `betterAuth({...})` call stays easy to edit without | |
| // breaking brackets (models often trip on the conditional plugin spread). | |
| const grokOAuthPlugin = grokClient | |
| ? genericOAuth({ | |
| config: GROK_PROVIDERS.map(({ providerId, idp }) => ({ | |
| providerId, | |
| clientId: grokClient.clientId, | |
| clientSecret: grokClient.clientSecret, | |
| // Prefer static endpoints over `discoveryUrl` so initiating (and | |
| // completing) OAuth does not wait on a broker discovery fetch. | |
| authorizationUrl: grokAuthorizationUrl, | |
| tokenUrl: grokTokenUrl, | |
| userInfoUrl: grokUserInfoUrl, | |
| scopes: ["openid", "profile", "email"], | |
| // `prompt: "login"` forces the broker to re-authenticate against the | |
| // upstream on every sign-in instead of silently reusing an existing | |
| // broker session. Combined with the broker sending Google | |
| // `prompt=select_account`, the user always gets the account chooser | |
| // and can pick (or switch) which account to sign in with. | |
| authorizationUrlParams: { idp, prompt: "login" }, | |
| })), | |
| }) | |
| : null; | |
| export const auth = betterAuth({ | |
| baseURL, | |
| // Deployed apps inject BETTER_AUTH_SECRET. Preview: process-stable secret on | |
| // globalThis so HMR doesn't invalidate PGLite-backed sessions (see above). | |
| secret: env("BETTER_AUTH_SECRET") ?? previewAuthSecret(), | |
| database, | |
| // CSRF / origin check for credentialed auth POSTs (email sign-up/sign-in, β¦). | |
| // See `trustedOrigins` construction above β must cover live preview hosts AND | |
| // local loopback variants, or clients get "Invalid origin". | |
| trustedOrigins, | |
| // Encrypt broker-issued OAuth tokens at rest, and treat the broker's upstreams | |
| // as trusted first-party identities. The broker owns identity and X emails are | |
| // synthetic/unverified, so WITHOUT this a login can fail with | |
| // `account_not_linked` (Better Auth refuses to attach an untrusted, unverified | |
| // identity to an existing user). Google and X carry DISTINCT emails, so this | |
| // never merges them into one user β they stay separate identities. | |
| account: { | |
| encryptOAuthTokens: true, | |
| accountLinking: { | |
| enabled: true, | |
| trustedProviders: [ | |
| ...GROK_PROVIDERS.map((p) => p.providerId), | |
| GATE_PROVIDER_ID, | |
| ], | |
| // X's synthetic email is never "verified", so don't gate linking on the | |
| // local user's email-verified state. | |
| requireLocalEmailVerified: false, | |
| }, | |
| }, | |
| // Cache the session in the short-lived signed `session_data` cookie so reads | |
| // (incl. the client's `/get-session`) skip the DB β this shrinks the "loading" | |
| // window and reduces auth flicker. See the `auth` skill for the full | |
| // flicker-prevention guidance (gate on `isPending`; SSR the session). | |
| session: { cookieCache: { enabled: true, maxAge: 300 } }, | |
| // Local email/password β toggled only via `./email-password` (not a plugin). | |
| ...(emailAndPasswordEnabled ? { emailAndPassword: { enabled: true } } : {}), | |
| // `__Host-` prefixed cookies: the browser REFUSES any same-named cookie that | |
| // carries a `Domain` attribute, so a sibling `*.grok.me` app cannot "toss" a | |
| // `Domain=.grok.me` session cookie onto this app. `__Host-` requires Secure + | |
| // Path=/ + no Domain; Better Auth otherwise uses `__Secure-` (which permits | |
| // Domain), so we drop its auto prefix (`useSecureCookies: false`) and set | |
| // Secure + the names ourselves. (Browsers allow Secure cookies on | |
| // `http://localhost`, so local dev still works.) | |
| advanced: { | |
| useSecureCookies: false, | |
| defaultCookieAttributes: { secure: true, sameSite: "lax", path: "/" }, | |
| cookies: { | |
| session_token: { name: SESSION_TOKEN_COOKIE }, | |
| session_data: { name: "__Host-grok-auth.session_data" }, | |
| account_data: { name: "__Host-grok-auth.account_data" }, | |
| dont_remember: { name: "__Host-grok-auth.dont_remember" }, | |
| }, | |
| }, | |
| plugins: [ | |
| gateIdentitySessions(), | |
| // One genericOAuth provider per upstream (when auth is on), all federating | |
| // to the broker with the SAME client and differing only by the `idp` hint. | |
| ...(grokOAuthPlugin ? [grokOAuthPlugin] : []), | |
| // Accept `Authorization: Bearer <session-token>` as an alternative to the | |
| // cookie. Needed for the LIVE PREVIEW: the app runs in an embedded iframe | |
| // where cookies are partitioned, so after popup sign-in it authenticates with | |
| // a bearer token instead (see `client.ts` / the `auth` skill). The hook only | |
| // fires when an Authorization header is present, so the cookie path | |
| // (deployed apps) is unaffected. | |
| bearer(), | |
| // Bridges Better Auth's Set-Cookie into TanStack Start responses. MUST be | |
| // last so it runs after every other plugin's hooks. | |
| tanstackStartCookies(), | |
| ], | |
| }); | |
| export function readSessionToken(): string | null { | |
| return getCookie(SESSION_TOKEN_COOKIE) ?? null; | |
| } | |
| // Re-exported for convenience; the array lives in the dependency-free | |
| // `providers.ts` so the client can import it too. | |
| export { GROK_PROVIDERS } from "./providers"; | |