@incorta/auth 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +61 -0
- package/dist/chunk-LNPECE7W.js +62 -0
- package/dist/chunk-LNPECE7W.js.map +1 -0
- package/dist/cli.js +170 -0
- package/dist/config-B-2cbZDV.d.cts +116 -0
- package/dist/config-B-2cbZDV.d.ts +116 -0
- package/dist/express.cjs +134 -0
- package/dist/express.cjs.map +1 -0
- package/dist/express.d.cts +42 -0
- package/dist/express.d.ts +42 -0
- package/dist/express.js +59 -0
- package/dist/express.js.map +1 -0
- package/dist/index.cjs +642 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +75 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +613 -0
- package/dist/index.js.map +1 -0
- package/dist/node.cjs +89 -0
- package/dist/node.cjs.map +1 -0
- package/dist/node.d.cts +28 -0
- package/dist/node.d.ts +28 -0
- package/dist/node.js +13 -0
- package/dist/node.js.map +1 -0
- package/dist/react/index.cjs +179 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +81 -0
- package/dist/react/index.d.ts +81 -0
- package/dist/react/index.js +153 -0
- package/dist/react/index.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/config.ts","../src/core/handler.ts","../src/core/cookies.ts","../src/core/discovery.ts","../src/core/session.ts","../src/core/tokens.ts","../src/core/register.ts","../src/index.ts"],"sourcesContent":["import type { JWTVerifyGetKey } from \"jose\";\n\n/** The Incorta user identity carried by the session (claims from the ID token). */\nexport interface IncortaUser {\n /** Incorta login name (`sub` claim). */\n sub: string;\n name?: string;\n email?: string;\n /** Incorta role names, as minted into the ID token. */\n roles: string[];\n /** Tenant the user authenticated against. */\n tenant: string;\n}\n\n/** What the sealed session cookie holds. */\nexport interface SessionData {\n user: IncortaUser;\n /** Incorta-issued access token (RS256 JWT, aud = client_id). */\n accessToken: string;\n /** Epoch millis when the access token expires. */\n accessTokenExpiresAt: number;\n /** Rotating refresh token, present when the client has the refresh_token grant. */\n refreshToken?: string;\n /** Epoch millis when this session was first created (login time). */\n createdAt: number;\n}\n\nexport interface SessionInfo {\n user: IncortaUser;\n /** Epoch millis when the session cookie expires (sliding). */\n expiresAt: number;\n /** Epoch millis when the access token expires. */\n accessTokenExpiresAt: number;\n}\n\nexport interface IncortaAuthConfig {\n /**\n * Base URL of the Incorta web application, e.g. `https://acme.cloud.incorta.com/incorta`\n * or `http://localhost:8080/incorta`. Defaults to `INCORTA_URL`.\n */\n incortaUrl?: string;\n /** Incorta tenant name, e.g. `demo`. Defaults to `INCORTA_TENANT`. */\n tenant?: string;\n /** OAuth client id (see `incorta-auth register`). Defaults to `INCORTA_CLIENT_ID`. */\n clientId?: string;\n /** OAuth client secret. Defaults to `INCORTA_CLIENT_SECRET`. */\n clientSecret?: string;\n /**\n * Secret used to encrypt the session cookie (any long random string, >= 32 chars).\n * Defaults to `INCORTA_AUTH_SECRET`.\n */\n secret?: string;\n /**\n * Path prefix the auth routes are mounted under. The SDK serves\n * `{basePath}/login`, `{basePath}/callback`, `{basePath}/session`,\n * `{basePath}/logout` (and optionally `{basePath}/token`).\n * @default \"/auth\"\n */\n basePath?: string;\n /**\n * External origin of THIS app, e.g. `http://localhost:4000`. Used to build the\n * OAuth callback URL and to validate `redirect_to` targets. When omitted it is\n * derived per request from `x-forwarded-proto`/`x-forwarded-host`/`host`.\n * Defaults to `INCORTA_APP_URL`.\n */\n appUrl?: string;\n /** OAuth scopes to request. @default \"openid profile email\" */\n scope?: string;\n session?: {\n /** @default \"incorta_auth_session\" */\n cookieName?: string;\n /** Session lifetime in seconds (sliding — renewed on activity). @default 43200 (12h) */\n maxAge?: number;\n };\n cookies?: {\n /**\n * `Secure` attribute for cookies. `\"auto\"` sets it when the request is HTTPS.\n * @default \"auto\"\n */\n secure?: boolean | \"auto\";\n };\n /**\n * Expose `GET {basePath}/token` returning the raw Incorta access token to the\n * browser (for calling Incorta public APIs directly from the frontend). Off by\n * default — prefer proxying through your backend.\n * @default false\n */\n exposeAccessToken?: boolean;\n advanced?: {\n /** Override the OIDC discovery document URL (rarely needed). */\n discoveryUrl?: string;\n /** Override JWKS resolution (used by tests to verify against a local key). */\n jwks?: JWTVerifyGetKey;\n /** Override fetch (used by tests). */\n fetch?: typeof fetch;\n };\n}\n\nexport interface ResolvedConfig {\n incortaUrl: string;\n tenant: string;\n clientId: string;\n clientSecret: string;\n secret: string;\n basePath: string;\n appUrl?: string;\n scope: string;\n cookieName: string;\n stateCookieName: string;\n sessionMaxAge: number;\n secureCookies: boolean | \"auto\";\n exposeAccessToken: boolean;\n /** `{incortaUrl}/oauth/{tenant}` — the OIDC issuer for this tenant. */\n issuer: string;\n discoveryUrl: string;\n jwksOverride?: JWTVerifyGetKey;\n fetch: typeof fetch;\n}\n\nfunction required(value: string | undefined, name: string, envVar: string): string {\n if (!value) {\n throw new Error(\n `[incorta-auth] Missing required config \"${name}\" (or env var ${envVar}).`,\n );\n }\n return value;\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\nexport function resolveConfig(input: IncortaAuthConfig = {}): ResolvedConfig {\n const env = (globalThis as { process?: { env?: Record<string, string | undefined> } })\n .process?.env ?? {};\n\n const incortaUrl = stripTrailingSlash(\n required(input.incortaUrl ?? env.INCORTA_URL, \"incortaUrl\", \"INCORTA_URL\"),\n );\n const tenant = required(input.tenant ?? env.INCORTA_TENANT, \"tenant\", \"INCORTA_TENANT\");\n const clientId = required(\n input.clientId ?? env.INCORTA_CLIENT_ID,\n \"clientId\",\n \"INCORTA_CLIENT_ID\",\n );\n const clientSecret = required(\n input.clientSecret ?? env.INCORTA_CLIENT_SECRET,\n \"clientSecret\",\n \"INCORTA_CLIENT_SECRET\",\n );\n const secret = required(\n input.secret ?? env.INCORTA_AUTH_SECRET,\n \"secret\",\n \"INCORTA_AUTH_SECRET\",\n );\n if (secret.length < 32) {\n throw new Error(\n \"[incorta-auth] `secret` must be at least 32 characters. Generate one with: openssl rand -base64 32\",\n );\n }\n\n const basePath = input.basePath ?? \"/auth\";\n if (!basePath.startsWith(\"/\") || basePath.endsWith(\"/\")) {\n throw new Error(\n `[incorta-auth] basePath must start with \"/\" and not end with \"/\" (got \"${basePath}\").`,\n );\n }\n\n const appUrl = input.appUrl ?? env.INCORTA_APP_URL;\n const cookieName = input.session?.cookieName ?? \"incorta_auth_session\";\n const issuer = `${incortaUrl}/oauth/${tenant}`;\n\n return {\n incortaUrl,\n tenant,\n clientId,\n clientSecret,\n secret,\n basePath,\n appUrl: appUrl ? stripTrailingSlash(appUrl) : undefined,\n scope: input.scope ?? \"openid profile email\",\n cookieName,\n stateCookieName: `${cookieName}_state`,\n sessionMaxAge: input.session?.maxAge ?? 12 * 60 * 60,\n secureCookies: input.cookies?.secure ?? \"auto\",\n exposeAccessToken: input.exposeAccessToken ?? false,\n issuer,\n discoveryUrl:\n input.advanced?.discoveryUrl ?? `${issuer}/.well-known/openid-configuration`,\n jwksOverride: input.advanced?.jwks,\n fetch: input.advanced?.fetch ?? fetch,\n };\n}\n","import { randomBytes } from \"node:crypto\";\nimport type { ResolvedConfig, SessionData, SessionInfo } from \"./config.js\";\nimport { deleteCookie, parseCookies, serializeCookie } from \"./cookies.js\";\nimport { getEndpoints } from \"./discovery.js\";\nimport {\n sealLoginState,\n sealSession,\n unsealLoginState,\n unsealSession,\n} from \"./session.js\";\nimport {\n IncortaAuthError,\n exchangeCode,\n refreshTokens,\n userFromAccessToken,\n verifyIdToken,\n} from \"./tokens.js\";\n\n/** A read-only view of the current session for server-side app code. */\nexport interface AuthSession extends SessionInfo {\n /** Incorta access token — pass as `Authorization: Bearer` to Incorta APIs. */\n accessToken: string;\n}\n\nexport type AuthHandler = (request: Request) => Promise<Response>;\n\n/** Refresh the access token when it has less than this long left to live. */\nconst ACCESS_TOKEN_REFRESH_LEEWAY_MS = 60 * 1000;\n\nfunction requestOrigin(config: ResolvedConfig, request: Request): string {\n if (config.appUrl) return config.appUrl;\n const url = new URL(request.url);\n const proto =\n request.headers.get(\"x-forwarded-proto\")?.split(\",\")[0]?.trim() ??\n url.protocol.replace(\":\", \"\");\n const host =\n request.headers.get(\"x-forwarded-host\")?.split(\",\")[0]?.trim() ??\n request.headers.get(\"host\") ??\n url.host;\n return `${proto}://${host}`;\n}\n\nfunction isSecureRequest(config: ResolvedConfig, request: Request): boolean {\n if (config.secureCookies !== \"auto\") return config.secureCookies;\n return requestOrigin(config, request).startsWith(\"https://\");\n}\n\n/**\n * Only same-origin destinations are allowed after login/logout — anything else\n * would let a crafted link bounce users through this app to an attacker page.\n */\nfunction validateReturnTo(raw: string | null, origin: string): string {\n if (!raw) return \"/\";\n if (raw.startsWith(\"/\") && !raw.startsWith(\"//\")) return raw;\n try {\n const url = new URL(raw);\n if (url.origin === origin) return `${url.pathname}${url.search}${url.hash}` || \"/\";\n } catch {\n // Not a URL — fall through.\n }\n return \"/\";\n}\n\nfunction json(status: number, body: unknown, cookies: string[] = []): Response {\n const headers = new Headers({\n \"content-type\": \"application/json; charset=utf-8\",\n \"cache-control\": \"no-store\",\n });\n for (const cookie of cookies) headers.append(\"set-cookie\", cookie);\n return new Response(JSON.stringify(body), { status, headers });\n}\n\nfunction redirect(location: string, cookies: string[] = []): Response {\n const headers = new Headers({ location, \"cache-control\": \"no-store\" });\n for (const cookie of cookies) headers.append(\"set-cookie\", cookie);\n return new Response(null, { status: 302, headers });\n}\n\nfunction withErrorParam(target: string, code: string): string {\n const separator = target.includes(\"?\") ? \"&\" : \"?\";\n return `${target}${separator}incorta_auth_error=${encodeURIComponent(code)}`;\n}\n\nfunction randomToken(): string {\n return randomBytes(16).toString(\"base64url\");\n}\n\nexport function createHandler(config: ResolvedConfig): {\n handler: AuthHandler;\n getSession: (request: Request) => Promise<AuthSession | null>;\n} {\n const sessionCookieOptions = (secure: boolean) => ({\n httpOnly: true,\n secure,\n sameSite: \"lax\" as const,\n path: \"/\",\n });\n\n async function login(request: Request, url: URL): Promise<Response> {\n const origin = requestOrigin(config, request);\n const secure = isSecureRequest(config, request);\n const returnTo = validateReturnTo(url.searchParams.get(\"redirect_to\"), origin);\n\n // Already signed in — skip the OAuth round trip entirely.\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n const existing = await unsealSession(config, cookies[config.cookieName]);\n if (existing) return redirect(returnTo);\n\n const state = randomToken();\n const nonce = randomToken();\n const stateCookie = serializeCookie(\n config.stateCookieName,\n await sealLoginState(config, { state, nonce, returnTo }),\n { ...sessionCookieOptions(secure), maxAge: 600 },\n );\n\n const { authorizationEndpoint } = await getEndpoints(config);\n const authorizeUrl = new URL(authorizationEndpoint);\n authorizeUrl.searchParams.set(\"response_type\", \"code\");\n authorizeUrl.searchParams.set(\"client_id\", config.clientId);\n authorizeUrl.searchParams.set(\"redirect_uri\", `${origin}${config.basePath}/callback`);\n authorizeUrl.searchParams.set(\"scope\", config.scope);\n authorizeUrl.searchParams.set(\"state\", state);\n authorizeUrl.searchParams.set(\"nonce\", nonce);\n\n return redirect(authorizeUrl.toString(), [stateCookie]);\n }\n\n async function callback(request: Request, url: URL): Promise<Response> {\n const origin = requestOrigin(config, request);\n const secure = isSecureRequest(config, request);\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n const clearState = deleteCookie(config.stateCookieName, sessionCookieOptions(secure));\n\n const loginState = await unsealLoginState(config, cookies[config.stateCookieName]);\n const returnTo = validateReturnTo(loginState?.returnTo ?? null, origin);\n\n const fail = (code: string) => redirect(withErrorParam(returnTo, code), [clearState]);\n\n const oauthError = url.searchParams.get(\"error\");\n if (oauthError) return fail(oauthError);\n\n const code = url.searchParams.get(\"code\");\n const state = url.searchParams.get(\"state\");\n if (!code || !state) return fail(\"missing_code\");\n if (!loginState) return fail(\"state_missing\");\n if (state !== loginState.state) return fail(\"state_mismatch\");\n\n try {\n const tokens = await exchangeCode(config, code, `${origin}${config.basePath}/callback`);\n const user = tokens.idToken\n ? await verifyIdToken(config, tokens.idToken, loginState.nonce)\n : await userFromAccessToken(config, tokens.accessToken);\n\n const session: SessionData = {\n user,\n accessToken: tokens.accessToken,\n accessTokenExpiresAt: tokens.accessTokenExpiresAt,\n refreshToken: tokens.refreshToken,\n createdAt: Date.now(),\n };\n const sessionCookie = serializeCookie(\n config.cookieName,\n await sealSession(config, session),\n { ...sessionCookieOptions(secure), maxAge: config.sessionMaxAge },\n );\n return redirect(returnTo, [sessionCookie, clearState]);\n } catch (error) {\n const code = error instanceof IncortaAuthError ? error.code : \"callback_failed\";\n return fail(code);\n }\n }\n\n /**\n * Loads the session from the request cookie; refreshes the Incorta access\n * token (rotating the refresh token) when it is about to expire, and renews\n * the sliding session cookie past its half-life. Returns the cookies to set.\n */\n async function loadSession(request: Request): Promise<{\n session: SessionData | null;\n sessionExpiresAt: number;\n setCookies: string[];\n }> {\n const secure = isSecureRequest(config, request);\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n const unsealed = await unsealSession(config, cookies[config.cookieName]);\n if (!unsealed) return { session: null, sessionExpiresAt: 0, setCookies: [] };\n\n let { session } = unsealed;\n let mustReseal = false;\n const now = Date.now();\n\n if (session.accessTokenExpiresAt - now < ACCESS_TOKEN_REFRESH_LEEWAY_MS) {\n if (session.refreshToken) {\n try {\n const tokens = await refreshTokens(config, session.refreshToken);\n session = {\n ...session,\n accessToken: tokens.accessToken,\n accessTokenExpiresAt: tokens.accessTokenExpiresAt,\n refreshToken: tokens.refreshToken ?? session.refreshToken,\n };\n mustReseal = true;\n } catch {\n // Refresh failed (expired/rotated elsewhere). Keep the session while\n // the current access token is still usable; otherwise sign out.\n if (session.accessTokenExpiresAt <= now) {\n return {\n session: null,\n sessionExpiresAt: 0,\n setCookies: [deleteCookie(config.cookieName, sessionCookieOptions(secure))],\n };\n }\n }\n } else if (session.accessTokenExpiresAt <= now) {\n return {\n session: null,\n sessionExpiresAt: 0,\n setCookies: [deleteCookie(config.cookieName, sessionCookieOptions(secure))],\n };\n }\n }\n\n const halfLifeMs = (config.sessionMaxAge * 1000) / 2;\n if (now - unsealed.issuedAt > halfLifeMs) mustReseal = true;\n\n if (!mustReseal) {\n return { session, sessionExpiresAt: unsealed.expiresAt, setCookies: [] };\n }\n const resealed = serializeCookie(config.cookieName, await sealSession(config, session), {\n ...sessionCookieOptions(secure),\n maxAge: config.sessionMaxAge,\n });\n return {\n session,\n sessionExpiresAt: now + config.sessionMaxAge * 1000,\n setCookies: [resealed],\n };\n }\n\n async function sessionRoute(request: Request): Promise<Response> {\n const { session, sessionExpiresAt, setCookies } = await loadSession(request);\n if (!session) return json(200, { user: null }, setCookies);\n return json(\n 200,\n {\n user: session.user,\n session: {\n expiresAt: sessionExpiresAt,\n accessTokenExpiresAt: session.accessTokenExpiresAt,\n },\n },\n setCookies,\n );\n }\n\n async function tokenRoute(request: Request): Promise<Response> {\n if (!config.exposeAccessToken) return json(404, { error: \"not_found\" });\n const { session, setCookies } = await loadSession(request);\n if (!session) return json(401, { error: \"unauthenticated\" }, setCookies);\n return json(\n 200,\n { accessToken: session.accessToken, expiresAt: session.accessTokenExpiresAt },\n setCookies,\n );\n }\n\n async function logout(request: Request, url: URL): Promise<Response> {\n const secure = isSecureRequest(config, request);\n const clearSession = deleteCookie(config.cookieName, sessionCookieOptions(secure));\n if (request.method === \"GET\") {\n const origin = requestOrigin(config, request);\n const returnTo = validateReturnTo(url.searchParams.get(\"redirect_to\"), origin);\n return redirect(returnTo, [clearSession]);\n }\n return json(200, { ok: true }, [clearSession]);\n }\n\n async function handler(request: Request): Promise<Response> {\n const url = new URL(request.url);\n if (url.pathname !== config.basePath && !url.pathname.startsWith(`${config.basePath}/`)) {\n return json(404, { error: \"not_found\" });\n }\n const route = url.pathname.slice(config.basePath.length) || \"/\";\n const method = request.method.toUpperCase();\n\n try {\n if (route === \"/login\" && method === \"GET\") return await login(request, url);\n if (route === \"/callback\" && method === \"GET\") return await callback(request, url);\n if (route === \"/session\" && method === \"GET\") return await sessionRoute(request);\n if (route === \"/token\" && method === \"GET\") return await tokenRoute(request);\n if (route === \"/logout\" && (method === \"POST\" || method === \"GET\")) {\n return await logout(request, url);\n }\n return json(404, { error: \"not_found\" });\n } catch (error) {\n const code = error instanceof IncortaAuthError ? error.code : \"internal_error\";\n const message =\n error instanceof Error ? error.message : \"Unexpected incorta-auth handler error\";\n console.error(`[incorta-auth] ${route} failed:`, message);\n return json(500, { error: code });\n }\n }\n\n async function getSession(request: Request): Promise<AuthSession | null> {\n const cookies = parseCookies(request.headers.get(\"cookie\"));\n const unsealed = await unsealSession(config, cookies[config.cookieName]);\n if (!unsealed) return null;\n const { session, expiresAt } = unsealed;\n if (session.accessTokenExpiresAt <= Date.now() && !session.refreshToken) return null;\n return {\n user: session.user,\n accessToken: session.accessToken,\n accessTokenExpiresAt: session.accessTokenExpiresAt,\n expiresAt,\n };\n }\n\n return { handler, getSession };\n}\n","export interface CookieOptions {\n httpOnly?: boolean;\n secure?: boolean;\n sameSite?: \"lax\" | \"strict\" | \"none\";\n path?: string;\n /** Seconds. 0 deletes the cookie. */\n maxAge?: number;\n}\n\nexport function parseCookies(header: string | null | undefined): Record<string, string> {\n const out: Record<string, string> = {};\n if (!header) return out;\n for (const part of header.split(\";\")) {\n const eq = part.indexOf(\"=\");\n if (eq === -1) continue;\n const name = part.slice(0, eq).trim();\n if (!name) continue;\n let value = part.slice(eq + 1).trim();\n if (value.startsWith('\"') && value.endsWith('\"')) value = value.slice(1, -1);\n try {\n out[name] = decodeURIComponent(value);\n } catch {\n out[name] = value;\n }\n }\n return out;\n}\n\nexport function serializeCookie(\n name: string,\n value: string,\n options: CookieOptions = {},\n): string {\n const parts = [`${name}=${encodeURIComponent(value)}`];\n parts.push(`Path=${options.path ?? \"/\"}`);\n if (options.maxAge !== undefined) {\n parts.push(`Max-Age=${Math.floor(options.maxAge)}`);\n // Expires for compatibility with clients that ignore Max-Age.\n parts.push(`Expires=${new Date(Date.now() + options.maxAge * 1000).toUTCString()}`);\n }\n if (options.httpOnly ?? true) parts.push(\"HttpOnly\");\n if (options.secure) parts.push(\"Secure\");\n const sameSite = options.sameSite ?? \"lax\";\n parts.push(`SameSite=${sameSite.charAt(0).toUpperCase()}${sameSite.slice(1)}`);\n return parts.join(\"; \");\n}\n\nexport function deleteCookie(name: string, options: CookieOptions = {}): string {\n return serializeCookie(name, \"\", { ...options, maxAge: 0 });\n}\n","import { createRemoteJWKSet } from \"jose\";\nimport type { JWTVerifyGetKey } from \"jose\";\nimport type { ResolvedConfig } from \"./config.js\";\n\ninterface DiscoveryDocument {\n issuer: string;\n authorization_endpoint: string;\n token_endpoint: string;\n jwks_uri: string;\n}\n\nexport interface Endpoints {\n issuer: string;\n authorizationEndpoint: string;\n tokenEndpoint: string;\n jwks: JWTVerifyGetKey;\n}\n\nconst endpointsCache = new Map<string, Promise<Endpoints>>();\n\n/**\n * Resolves the tenant's OAuth endpoints, preferring the OIDC discovery document\n * and falling back to Incorta's fixed `/oauth/{tenant}/...` layout when the\n * document is unreachable. Cached per issuer for the process lifetime.\n */\nexport function getEndpoints(config: ResolvedConfig): Promise<Endpoints> {\n const cached = endpointsCache.get(config.issuer);\n if (cached) return cached;\n\n const promise = resolve(config).catch((error) => {\n // Do not cache failures — a temporarily unreachable Incorta should not\n // poison the process.\n endpointsCache.delete(config.issuer);\n throw error;\n });\n endpointsCache.set(config.issuer, promise);\n return promise;\n}\n\nasync function resolve(config: ResolvedConfig): Promise<Endpoints> {\n const fallback: DiscoveryDocument = {\n issuer: config.issuer,\n authorization_endpoint: `${config.issuer}/authorize`,\n token_endpoint: `${config.issuer}/token`,\n jwks_uri: `${config.issuer}/.well-known/jwks.json`,\n };\n\n let doc = fallback;\n try {\n const response = await config.fetch(config.discoveryUrl, {\n headers: { accept: \"application/json\" },\n });\n if (response.ok) {\n const body = (await response.json()) as Partial<DiscoveryDocument>;\n doc = {\n issuer: body.issuer ?? fallback.issuer,\n authorization_endpoint: body.authorization_endpoint ?? fallback.authorization_endpoint,\n token_endpoint: body.token_endpoint ?? fallback.token_endpoint,\n jwks_uri: body.jwks_uri ?? fallback.jwks_uri,\n };\n }\n } catch {\n // Fall through to the fixed layout.\n }\n\n const jwks =\n config.jwksOverride ??\n createRemoteJWKSet(new URL(doc.jwks_uri), {\n cacheMaxAge: 10 * 60 * 1000,\n cooldownDuration: 30 * 1000,\n });\n\n return {\n issuer: doc.issuer,\n authorizationEndpoint: doc.authorization_endpoint,\n tokenEndpoint: doc.token_endpoint,\n jwks,\n };\n}\n\n/** Test hook: clears the per-process endpoint cache. */\nexport function clearEndpointsCache(): void {\n endpointsCache.clear();\n}\n","import { createHash } from \"node:crypto\";\nimport { EncryptJWT, jwtDecrypt } from \"jose\";\nimport type { ResolvedConfig, SessionData } from \"./config.js\";\n\n/**\n * Session and state cookies are sealed as encrypted JWTs (JWE, dir + A256GCM):\n * tamper-proof AND opaque, since the session carries the Incorta access/refresh\n * tokens. The 256-bit key is derived from the configured secret.\n */\nfunction sealKey(config: ResolvedConfig): Uint8Array {\n return new Uint8Array(\n createHash(\"sha256\").update(`incorta-auth:${config.secret}`).digest(),\n );\n}\n\ninterface SealedPayload {\n /** Guards against replaying one cookie kind as the other. */\n purpose: \"session\" | \"state\";\n data: unknown;\n}\n\nasync function seal(\n config: ResolvedConfig,\n purpose: SealedPayload[\"purpose\"],\n data: unknown,\n maxAgeSeconds: number,\n): Promise<string> {\n return new EncryptJWT({ purpose, data })\n .setProtectedHeader({ alg: \"dir\", enc: \"A256GCM\" })\n .setIssuedAt()\n .setExpirationTime(`${maxAgeSeconds}s`)\n .encrypt(sealKey(config));\n}\n\nasync function unseal<T>(\n config: ResolvedConfig,\n purpose: SealedPayload[\"purpose\"],\n cookieValue: string | undefined,\n): Promise<{ data: T; issuedAt: number; expiresAt: number } | null> {\n if (!cookieValue) return null;\n try {\n const { payload } = await jwtDecrypt(cookieValue, sealKey(config));\n if (payload.purpose !== purpose) return null;\n return {\n data: payload.data as T,\n issuedAt: (payload.iat ?? 0) * 1000,\n expiresAt: (payload.exp ?? 0) * 1000,\n };\n } catch {\n // Expired, tampered, or sealed with a different secret — treat as absent.\n return null;\n }\n}\n\nexport function sealSession(config: ResolvedConfig, session: SessionData): Promise<string> {\n return seal(config, \"session\", session, config.sessionMaxAge);\n}\n\nexport async function unsealSession(\n config: ResolvedConfig,\n cookieValue: string | undefined,\n): Promise<{ session: SessionData; issuedAt: number; expiresAt: number } | null> {\n const result = await unseal<SessionData>(config, \"session\", cookieValue);\n if (!result) return null;\n const { data, issuedAt, expiresAt } = result;\n if (!data || typeof data !== \"object\" || !data.user || !data.accessToken) return null;\n return { session: data, issuedAt, expiresAt };\n}\n\n/** Login round-trip state (CSRF protection + post-login destination). */\nexport interface LoginState {\n state: string;\n nonce: string;\n returnTo: string;\n}\n\nconst STATE_MAX_AGE_SECONDS = 10 * 60;\n\nexport function sealLoginState(config: ResolvedConfig, state: LoginState): Promise<string> {\n return seal(config, \"state\", state, STATE_MAX_AGE_SECONDS);\n}\n\nexport async function unsealLoginState(\n config: ResolvedConfig,\n cookieValue: string | undefined,\n): Promise<LoginState | null> {\n const result = await unseal<LoginState>(config, \"state\", cookieValue);\n if (!result) return null;\n const { data } = result;\n if (!data || typeof data.state !== \"string\" || typeof data.nonce !== \"string\") return null;\n return data;\n}\n","import { decodeJwt, jwtVerify } from \"jose\";\nimport type { JWTPayload } from \"jose\";\nimport type { IncortaUser, ResolvedConfig } from \"./config.js\";\nimport { getEndpoints } from \"./discovery.js\";\n\nexport interface TokenSet {\n accessToken: string;\n /** Epoch millis derived from the access token's `exp` claim. */\n accessTokenExpiresAt: number;\n idToken?: string;\n refreshToken?: string;\n}\n\ninterface TokenEndpointResponse {\n access_token?: string;\n id_token?: string;\n refresh_token?: string;\n error?: string;\n error_description?: string;\n}\n\nexport class IncortaAuthError extends Error {\n constructor(\n readonly code: string,\n message: string,\n ) {\n super(`[incorta-auth] ${message}`);\n this.name = \"IncortaAuthError\";\n }\n}\n\nfunction basicAuth(config: ResolvedConfig): string {\n const raw = `${config.clientId}:${config.clientSecret}`;\n return `Basic ${Buffer.from(raw, \"utf8\").toString(\"base64\")}`;\n}\n\nasync function callTokenEndpoint(\n config: ResolvedConfig,\n params: Record<string, string>,\n): Promise<TokenSet> {\n const { tokenEndpoint } = await getEndpoints(config);\n\n let response: Response;\n try {\n response = await config.fetch(tokenEndpoint, {\n method: \"POST\",\n headers: {\n \"content-type\": \"application/x-www-form-urlencoded\",\n accept: \"application/json\",\n authorization: basicAuth(config),\n },\n body: new URLSearchParams(params).toString(),\n });\n } catch (error) {\n throw new IncortaAuthError(\n \"incorta_unreachable\",\n `Could not reach the Incorta token endpoint (${tokenEndpoint}): ${(error as Error).message}`,\n );\n }\n\n let body: TokenEndpointResponse;\n try {\n body = (await response.json()) as TokenEndpointResponse;\n } catch {\n throw new IncortaAuthError(\n \"invalid_token_response\",\n `Token endpoint returned a non-JSON response (HTTP ${response.status}).`,\n );\n }\n\n if (!response.ok || !body.access_token) {\n throw new IncortaAuthError(\n body.error ?? \"token_exchange_failed\",\n body.error_description ?? `Token exchange failed (HTTP ${response.status}).`,\n );\n }\n\n const { exp } = decodeJwt(body.access_token);\n return {\n accessToken: body.access_token,\n accessTokenExpiresAt: exp ? exp * 1000 : Date.now() + 60 * 60 * 1000,\n idToken: body.id_token,\n refreshToken: body.refresh_token,\n };\n}\n\nexport function exchangeCode(\n config: ResolvedConfig,\n code: string,\n redirectUri: string,\n): Promise<TokenSet> {\n return callTokenEndpoint(config, {\n grant_type: \"authorization_code\",\n code,\n redirect_uri: redirectUri,\n });\n}\n\nexport function refreshTokens(config: ResolvedConfig, refreshToken: string): Promise<TokenSet> {\n return callTokenEndpoint(config, {\n grant_type: \"refresh_token\",\n refresh_token: refreshToken,\n });\n}\n\nasync function verifyJwt(config: ResolvedConfig, token: string): Promise<JWTPayload> {\n const { jwks, issuer } = await getEndpoints(config);\n try {\n const { payload } = await jwtVerify(token, jwks, {\n issuer,\n audience: config.clientId,\n algorithms: [\"RS256\"],\n });\n return payload;\n } catch (error) {\n throw new IncortaAuthError(\n \"invalid_token\",\n `Token verification failed: ${(error as Error).message}`,\n );\n }\n}\n\nexport async function verifyAccessToken(\n config: ResolvedConfig,\n accessToken: string,\n): Promise<JWTPayload> {\n return verifyJwt(config, accessToken);\n}\n\n/**\n * Verifies the ID token (signature, issuer, audience, nonce) and maps its\n * claims to the SDK's user shape.\n */\nexport async function verifyIdToken(\n config: ResolvedConfig,\n idToken: string,\n expectedNonce: string,\n): Promise<IncortaUser> {\n const payload = await verifyJwt(config, idToken);\n\n if (payload.nonce !== expectedNonce) {\n throw new IncortaAuthError(\"nonce_mismatch\", \"ID token nonce does not match the login request.\");\n }\n if (typeof payload.sub !== \"string\" || !payload.sub) {\n throw new IncortaAuthError(\"invalid_token\", \"ID token is missing the sub claim.\");\n }\n\n const roles = Array.isArray(payload.roles)\n ? payload.roles.filter((role): role is string => typeof role === \"string\")\n : [];\n\n return {\n sub: payload.sub,\n name: typeof payload.name === \"string\" ? payload.name : undefined,\n email: typeof payload.email === \"string\" ? payload.email : undefined,\n roles,\n tenant: typeof payload.tenant === \"string\" ? payload.tenant : config.tenant,\n };\n}\n\n/**\n * Fallback identity when the client was registered without the `openid` scope\n * (no ID token in the response): identify the user from the verified access\n * token instead. Name/email/roles are unavailable in that case.\n */\nexport async function userFromAccessToken(\n config: ResolvedConfig,\n accessToken: string,\n): Promise<IncortaUser> {\n const payload = await verifyAccessToken(config, accessToken);\n if (typeof payload.sub !== \"string\" || !payload.sub) {\n throw new IncortaAuthError(\"invalid_token\", \"Access token is missing the sub claim.\");\n }\n return {\n sub: payload.sub,\n roles: [],\n tenant: typeof payload.tenant === \"string\" ? payload.tenant : config.tenant,\n };\n}\n","export interface RegisterClientOptions {\n /** Incorta base URL, e.g. `http://localhost:8080/incorta`. */\n incortaUrl: string;\n /** Tenant to register the client in. */\n tenant: string;\n /** Display name of the client. */\n name: string;\n /** Allowed OAuth callback URLs, e.g. `http://localhost:4000/auth/callback`. */\n redirectUris: string[];\n /** @default \"openid profile email\" */\n scope?: string;\n /** @default [\"authorization_code\", \"refresh_token\"] */\n grantTypes?: string[];\n fetchImpl?: typeof fetch;\n}\n\nexport interface RegisteredClient {\n clientId: string;\n clientSecret: string;\n /** Bearer token for managing this registration (GET/PUT/DELETE it later). */\n registrationAccessToken?: string;\n /** URL to manage this registration. */\n registrationClientUri?: string;\n}\n\n/**\n * Registers an OAuth client in Incorta via RFC 7591 dynamic client\n * registration (`POST {incortaUrl}/oauth/{tenant}/register`). Incorta allows\n * unauthenticated registration for authorization_code clients; the response\n * carries the generated client_id/client_secret — store them once, e.g. in the\n * app's `.env`.\n */\nexport async function registerClient(\n options: RegisterClientOptions,\n): Promise<RegisteredClient> {\n const {\n incortaUrl,\n tenant,\n name,\n redirectUris,\n scope = \"openid profile email\",\n grantTypes = [\"authorization_code\", \"refresh_token\"],\n fetchImpl = fetch,\n } = options;\n\n if (!redirectUris.length) {\n throw new Error(\"[incorta-auth] registerClient requires at least one redirect URI.\");\n }\n\n const endpoint = `${incortaUrl.replace(/\\/+$/, \"\")}/oauth/${tenant}/register`;\n const response = await fetchImpl(endpoint, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\", accept: \"application/json\" },\n body: JSON.stringify({\n client_name: name,\n redirect_uris: redirectUris,\n grant_types: grantTypes,\n scope,\n token_endpoint_auth_method: \"client_secret_basic\",\n }),\n });\n\n interface RegistrationResponse {\n client_id?: string;\n client_secret?: string;\n registration_access_token?: string;\n registration_client_uri?: string;\n error?: string;\n error_description?: string;\n }\n\n let body: RegistrationResponse;\n try {\n body = (await response.json()) as RegistrationResponse;\n } catch {\n throw new Error(\n `[incorta-auth] Client registration returned a non-JSON response (HTTP ${response.status}). ` +\n \"Is the OAuth authorization server enabled on this cluster?\",\n );\n }\n\n if (!response.ok || !body.client_id || !body.client_secret) {\n throw new Error(\n `[incorta-auth] Client registration failed (HTTP ${response.status}): ` +\n `${body.error ?? \"unknown_error\"} — ${body.error_description ?? \"no description\"}`,\n );\n }\n\n return {\n clientId: body.client_id,\n clientSecret: body.client_secret,\n registrationAccessToken: body.registration_access_token,\n registrationClientUri: body.registration_client_uri,\n };\n}\n","import { resolveConfig } from \"./core/config.js\";\nimport type { IncortaAuthConfig, ResolvedConfig } from \"./core/config.js\";\nimport { createHandler } from \"./core/handler.js\";\nimport type { AuthHandler, AuthSession } from \"./core/handler.js\";\n\nexport type {\n IncortaAuthConfig,\n IncortaUser,\n SessionData,\n SessionInfo,\n} from \"./core/config.js\";\nexport type { AuthHandler, AuthSession } from \"./core/handler.js\";\nexport { IncortaAuthError } from \"./core/tokens.js\";\nexport { registerClient } from \"./core/register.js\";\nexport type { RegisterClientOptions, RegisteredClient } from \"./core/register.js\";\n\nexport interface IncortaAuth {\n /**\n * Web-standard request handler serving the auth routes under\n * `config.basePath` (default `/auth`): `/login`, `/callback`, `/session`,\n * `/logout` (+ `/token` when `exposeAccessToken` is on).\n *\n * Wire it to anything that speaks `Request`/`Response` — a TanStack Start\n * server route, Bun.serve, or via `@incorta/auth/node` / `@incorta/auth/express`\n * for Node servers.\n */\n handler: AuthHandler;\n /**\n * Reads the session from a web-standard `Request` (server-side gating, API\n * routes, server functions). Returns null when the user is not signed in.\n */\n getSession: (request: Request) => Promise<AuthSession | null>;\n /** The resolved configuration (secrets included — do not log). */\n config: ResolvedConfig;\n}\n\n/**\n * Creates an IncortaAuth instance: OAuth 2.0 authorization-code flow against\n * Incorta's built-in authorization server. Unauthenticated users are redirected\n * to the Incorta login page and returned to your app once signed in.\n */\nexport function createIncortaAuth(config: IncortaAuthConfig = {}): IncortaAuth {\n const resolved = resolveConfig(config);\n const { handler, getSession } = createHandler(resolved);\n return { handler, getSession, config: resolved };\n}\n"],"mappings":"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","names":["code"]}
|
package/dist/node.cjs
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/node.ts
|
|
21
|
+
var node_exports = {};
|
|
22
|
+
__export(node_exports, {
|
|
23
|
+
getNodeSession: () => getNodeSession,
|
|
24
|
+
sendWebResponse: () => sendWebResponse,
|
|
25
|
+
toNodeHandler: () => toNodeHandler,
|
|
26
|
+
toWebRequest: () => toWebRequest
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(node_exports);
|
|
29
|
+
async function toWebRequest(req) {
|
|
30
|
+
const proto = req.headers["x-forwarded-proto"]?.split(",")[0]?.trim() ?? (req.socket.encrypted ? "https" : "http");
|
|
31
|
+
const host = req.headers.host ?? "localhost";
|
|
32
|
+
const path = req.originalUrl ?? req.url ?? "/";
|
|
33
|
+
const url = `${proto}://${host}${path}`;
|
|
34
|
+
const headers = new Headers();
|
|
35
|
+
for (let i = 0; i < req.rawHeaders.length; i += 2) {
|
|
36
|
+
headers.append(req.rawHeaders[i], req.rawHeaders[i + 1]);
|
|
37
|
+
}
|
|
38
|
+
const method = req.method ?? "GET";
|
|
39
|
+
let body;
|
|
40
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
41
|
+
const chunks = [];
|
|
42
|
+
for await (const chunk of req) {
|
|
43
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
44
|
+
}
|
|
45
|
+
if (chunks.length) {
|
|
46
|
+
const bytes = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));
|
|
47
|
+
let offset = 0;
|
|
48
|
+
for (const chunk of chunks) {
|
|
49
|
+
bytes.set(chunk, offset);
|
|
50
|
+
offset += chunk.length;
|
|
51
|
+
}
|
|
52
|
+
body = bytes;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return new Request(url, { method, headers, body });
|
|
56
|
+
}
|
|
57
|
+
async function sendWebResponse(res, response) {
|
|
58
|
+
res.statusCode = response.status;
|
|
59
|
+
response.headers.forEach((value, name) => {
|
|
60
|
+
if (name.toLowerCase() === "set-cookie") return;
|
|
61
|
+
res.setHeader(name, value);
|
|
62
|
+
});
|
|
63
|
+
const setCookies = response.headers.getSetCookie();
|
|
64
|
+
if (setCookies.length) res.setHeader("set-cookie", setCookies);
|
|
65
|
+
if (response.body) {
|
|
66
|
+
const buffer = Buffer.from(await response.arrayBuffer());
|
|
67
|
+
res.end(buffer);
|
|
68
|
+
} else {
|
|
69
|
+
res.end();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function toNodeHandler(auth) {
|
|
73
|
+
return async (req, res) => {
|
|
74
|
+
const request = await toWebRequest(req);
|
|
75
|
+
const response = await auth.handler(request);
|
|
76
|
+
await sendWebResponse(res, response);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function getNodeSession(auth, req) {
|
|
80
|
+
return auth.getSession(await toWebRequest(req));
|
|
81
|
+
}
|
|
82
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
83
|
+
0 && (module.exports = {
|
|
84
|
+
getNodeSession,
|
|
85
|
+
sendWebResponse,
|
|
86
|
+
toNodeHandler,
|
|
87
|
+
toWebRequest
|
|
88
|
+
});
|
|
89
|
+
//# sourceMappingURL=node.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/node.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse } from \"node:http\";\nimport type { IncortaAuth } from \"./index.js\";\n\nexport type { IncortaAuth } from \"./index.js\";\n\n/**\n * Builds a web-standard `Request` from a Node request. Buffers the body (the\n * auth routes are GET/POST-without-body, so this stays tiny).\n */\nexport async function toWebRequest(req: IncomingMessage): Promise<Request> {\n const proto =\n (req.headers[\"x-forwarded-proto\"] as string | undefined)?.split(\",\")[0]?.trim() ??\n ((req.socket as { encrypted?: boolean }).encrypted ? \"https\" : \"http\");\n const host = req.headers.host ?? \"localhost\";\n // Express rewrites req.url when routers are mounted — originalUrl keeps the full path.\n const path = (req as { originalUrl?: string }).originalUrl ?? req.url ?? \"/\";\n const url = `${proto}://${host}${path}`;\n\n const headers = new Headers();\n for (let i = 0; i < req.rawHeaders.length; i += 2) {\n headers.append(req.rawHeaders[i], req.rawHeaders[i + 1]);\n }\n\n const method = req.method ?? \"GET\";\n let body: BodyInit | undefined;\n if (method !== \"GET\" && method !== \"HEAD\") {\n const chunks: Buffer[] = [];\n for await (const chunk of req) {\n chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n }\n if (chunks.length) {\n const bytes = new Uint8Array(chunks.reduce((n, c) => n + c.length, 0));\n let offset = 0;\n for (const chunk of chunks) {\n bytes.set(chunk, offset);\n offset += chunk.length;\n }\n body = bytes;\n }\n }\n\n return new Request(url, { method, headers, body });\n}\n\nexport async function sendWebResponse(\n res: ServerResponse,\n response: Response,\n): Promise<void> {\n res.statusCode = response.status;\n response.headers.forEach((value, name) => {\n if (name.toLowerCase() === \"set-cookie\") return; // handled below, needs the un-joined list\n res.setHeader(name, value);\n });\n const setCookies = response.headers.getSetCookie();\n if (setCookies.length) res.setHeader(\"set-cookie\", setCookies);\n\n if (response.body) {\n const buffer = Buffer.from(await response.arrayBuffer());\n res.end(buffer);\n } else {\n res.end();\n }\n}\n\n/**\n * Adapts the auth handler to a plain Node `(req, res)` listener:\n *\n * ```ts\n * import http from \"node:http\";\n * const authListener = toNodeHandler(auth);\n * http.createServer((req, res) => {\n * if (req.url?.startsWith(\"/auth\")) return authListener(req, res);\n * // ... your app\n * });\n * ```\n */\nexport function toNodeHandler(auth: IncortaAuth) {\n return async (req: IncomingMessage, res: ServerResponse): Promise<void> => {\n const request = await toWebRequest(req);\n const response = await auth.handler(request);\n await sendWebResponse(res, response);\n };\n}\n\n/** Reads the current session from a Node request (returns null when signed out). */\nexport async function getNodeSession(auth: IncortaAuth, req: IncomingMessage) {\n return auth.getSession(await toWebRequest(req));\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,eAAsB,aAAa,KAAwC;AACzE,QAAM,QACH,IAAI,QAAQ,mBAAmB,GAA0B,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,MAC5E,IAAI,OAAmC,YAAY,UAAU;AACjE,QAAM,OAAO,IAAI,QAAQ,QAAQ;AAEjC,QAAM,OAAQ,IAAiC,eAAe,IAAI,OAAO;AACzE,QAAM,MAAM,GAAG,KAAK,MAAM,IAAI,GAAG,IAAI;AAErC,QAAM,UAAU,IAAI,QAAQ;AAC5B,WAAS,IAAI,GAAG,IAAI,IAAI,WAAW,QAAQ,KAAK,GAAG;AACjD,YAAQ,OAAO,IAAI,WAAW,CAAC,GAAG,IAAI,WAAW,IAAI,CAAC,CAAC;AAAA,EACzD;AAEA,QAAM,SAAS,IAAI,UAAU;AAC7B,MAAI;AACJ,MAAI,WAAW,SAAS,WAAW,QAAQ;AACzC,UAAM,SAAmB,CAAC;AAC1B,qBAAiB,SAAS,KAAK;AAC7B,aAAO,KAAK,OAAO,SAAS,KAAK,IAAI,QAAQ,OAAO,KAAK,KAAK,CAAC;AAAA,IACjE;AACA,QAAI,OAAO,QAAQ;AACjB,YAAM,QAAQ,IAAI,WAAW,OAAO,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,QAAQ,CAAC,CAAC;AACrE,UAAI,SAAS;AACb,iBAAW,SAAS,QAAQ;AAC1B,cAAM,IAAI,OAAO,MAAM;AACvB,kBAAU,MAAM;AAAA,MAClB;AACA,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO,IAAI,QAAQ,KAAK,EAAE,QAAQ,SAAS,KAAK,CAAC;AACnD;AAEA,eAAsB,gBACpB,KACA,UACe;AACf,MAAI,aAAa,SAAS;AAC1B,WAAS,QAAQ,QAAQ,CAAC,OAAO,SAAS;AACxC,QAAI,KAAK,YAAY,MAAM,aAAc;AACzC,QAAI,UAAU,MAAM,KAAK;AAAA,EAC3B,CAAC;AACD,QAAM,aAAa,SAAS,QAAQ,aAAa;AACjD,MAAI,WAAW,OAAQ,KAAI,UAAU,cAAc,UAAU;AAE7D,MAAI,SAAS,MAAM;AACjB,UAAM,SAAS,OAAO,KAAK,MAAM,SAAS,YAAY,CAAC;AACvD,QAAI,IAAI,MAAM;AAAA,EAChB,OAAO;AACL,QAAI,IAAI;AAAA,EACV;AACF;AAcO,SAAS,cAAc,MAAmB;AAC/C,SAAO,OAAO,KAAsB,QAAuC;AACzE,UAAM,UAAU,MAAM,aAAa,GAAG;AACtC,UAAM,WAAW,MAAM,KAAK,QAAQ,OAAO;AAC3C,UAAM,gBAAgB,KAAK,QAAQ;AAAA,EACrC;AACF;AAGA,eAAsB,eAAe,MAAmB,KAAsB;AAC5E,SAAO,KAAK,WAAW,MAAM,aAAa,GAAG,CAAC;AAChD;","names":[]}
|
package/dist/node.d.cts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IncortaAuth, AuthSession } from './index.cjs';
|
|
2
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
import './config-B-2cbZDV.cjs';
|
|
4
|
+
import 'jose';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Builds a web-standard `Request` from a Node request. Buffers the body (the
|
|
8
|
+
* auth routes are GET/POST-without-body, so this stays tiny).
|
|
9
|
+
*/
|
|
10
|
+
declare function toWebRequest(req: IncomingMessage): Promise<Request>;
|
|
11
|
+
declare function sendWebResponse(res: ServerResponse, response: Response): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Adapts the auth handler to a plain Node `(req, res)` listener:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import http from "node:http";
|
|
17
|
+
* const authListener = toNodeHandler(auth);
|
|
18
|
+
* http.createServer((req, res) => {
|
|
19
|
+
* if (req.url?.startsWith("/auth")) return authListener(req, res);
|
|
20
|
+
* // ... your app
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare function toNodeHandler(auth: IncortaAuth): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
25
|
+
/** Reads the current session from a Node request (returns null when signed out). */
|
|
26
|
+
declare function getNodeSession(auth: IncortaAuth, req: IncomingMessage): Promise<AuthSession | null>;
|
|
27
|
+
|
|
28
|
+
export { IncortaAuth, getNodeSession, sendWebResponse, toNodeHandler, toWebRequest };
|
package/dist/node.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { IncortaAuth, AuthSession } from './index.js';
|
|
2
|
+
import { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
import './config-B-2cbZDV.js';
|
|
4
|
+
import 'jose';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Builds a web-standard `Request` from a Node request. Buffers the body (the
|
|
8
|
+
* auth routes are GET/POST-without-body, so this stays tiny).
|
|
9
|
+
*/
|
|
10
|
+
declare function toWebRequest(req: IncomingMessage): Promise<Request>;
|
|
11
|
+
declare function sendWebResponse(res: ServerResponse, response: Response): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Adapts the auth handler to a plain Node `(req, res)` listener:
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* import http from "node:http";
|
|
17
|
+
* const authListener = toNodeHandler(auth);
|
|
18
|
+
* http.createServer((req, res) => {
|
|
19
|
+
* if (req.url?.startsWith("/auth")) return authListener(req, res);
|
|
20
|
+
* // ... your app
|
|
21
|
+
* });
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
declare function toNodeHandler(auth: IncortaAuth): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
25
|
+
/** Reads the current session from a Node request (returns null when signed out). */
|
|
26
|
+
declare function getNodeSession(auth: IncortaAuth, req: IncomingMessage): Promise<AuthSession | null>;
|
|
27
|
+
|
|
28
|
+
export { IncortaAuth, getNodeSession, sendWebResponse, toNodeHandler, toWebRequest };
|
package/dist/node.js
ADDED
package/dist/node.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/react/index.ts
|
|
21
|
+
var react_exports = {};
|
|
22
|
+
__export(react_exports, {
|
|
23
|
+
IncortaAuthProvider: () => IncortaAuthProvider,
|
|
24
|
+
Protect: () => Protect,
|
|
25
|
+
RedirectToLogin: () => RedirectToLogin,
|
|
26
|
+
SignedIn: () => SignedIn,
|
|
27
|
+
SignedOut: () => SignedOut,
|
|
28
|
+
useAuth: () => useAuth,
|
|
29
|
+
useIncortaAuth: () => useIncortaAuth,
|
|
30
|
+
useUser: () => useUser
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(react_exports);
|
|
33
|
+
|
|
34
|
+
// src/react/context.tsx
|
|
35
|
+
var import_react = require("react");
|
|
36
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
37
|
+
var IncortaAuthContext = (0, import_react.createContext)(null);
|
|
38
|
+
function IncortaAuthProvider({
|
|
39
|
+
children,
|
|
40
|
+
basePath = "/auth",
|
|
41
|
+
refetchInterval = 5 * 60 * 1e3
|
|
42
|
+
}) {
|
|
43
|
+
const [isLoaded, setIsLoaded] = (0, import_react.useState)(false);
|
|
44
|
+
const [user, setUser] = (0, import_react.useState)(null);
|
|
45
|
+
const [sessionExpiresAt, setSessionExpiresAt] = (0, import_react.useState)(null);
|
|
46
|
+
const mounted = (0, import_react.useRef)(true);
|
|
47
|
+
const refresh = (0, import_react.useCallback)(async () => {
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(`${basePath}/session`, {
|
|
50
|
+
headers: { accept: "application/json" }
|
|
51
|
+
});
|
|
52
|
+
const body = await response.json();
|
|
53
|
+
if (!mounted.current) return;
|
|
54
|
+
setUser(body.user ?? null);
|
|
55
|
+
setSessionExpiresAt(body.session?.expiresAt ?? null);
|
|
56
|
+
} catch {
|
|
57
|
+
if (!mounted.current) return;
|
|
58
|
+
setUser(null);
|
|
59
|
+
setSessionExpiresAt(null);
|
|
60
|
+
} finally {
|
|
61
|
+
if (mounted.current) setIsLoaded(true);
|
|
62
|
+
}
|
|
63
|
+
}, [basePath]);
|
|
64
|
+
(0, import_react.useEffect)(() => {
|
|
65
|
+
mounted.current = true;
|
|
66
|
+
void refresh();
|
|
67
|
+
return () => {
|
|
68
|
+
mounted.current = false;
|
|
69
|
+
};
|
|
70
|
+
}, [refresh]);
|
|
71
|
+
(0, import_react.useEffect)(() => {
|
|
72
|
+
if (!refetchInterval) return;
|
|
73
|
+
const id = setInterval(() => void refresh(), refetchInterval);
|
|
74
|
+
return () => clearInterval(id);
|
|
75
|
+
}, [refetchInterval, refresh]);
|
|
76
|
+
const signIn = (0, import_react.useCallback)(
|
|
77
|
+
(options) => {
|
|
78
|
+
if (typeof window === "undefined") return;
|
|
79
|
+
const current = `${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
80
|
+
const redirectTo = options?.redirectTo ?? current;
|
|
81
|
+
window.location.assign(
|
|
82
|
+
`${basePath}/login?redirect_to=${encodeURIComponent(redirectTo)}`
|
|
83
|
+
);
|
|
84
|
+
},
|
|
85
|
+
[basePath]
|
|
86
|
+
);
|
|
87
|
+
const signOut = (0, import_react.useCallback)(
|
|
88
|
+
async (options) => {
|
|
89
|
+
try {
|
|
90
|
+
await fetch(`${basePath}/logout`, { method: "POST" });
|
|
91
|
+
} finally {
|
|
92
|
+
setUser(null);
|
|
93
|
+
setSessionExpiresAt(null);
|
|
94
|
+
if (options?.redirectTo && typeof window !== "undefined") {
|
|
95
|
+
window.location.assign(options.redirectTo);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
[basePath]
|
|
100
|
+
);
|
|
101
|
+
const getToken = (0, import_react.useCallback)(async () => {
|
|
102
|
+
try {
|
|
103
|
+
const response = await fetch(`${basePath}/token`, {
|
|
104
|
+
headers: { accept: "application/json" }
|
|
105
|
+
});
|
|
106
|
+
if (!response.ok) return null;
|
|
107
|
+
const body = await response.json();
|
|
108
|
+
return body.accessToken ?? null;
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}, [basePath]);
|
|
113
|
+
const value = (0, import_react.useMemo)(
|
|
114
|
+
() => ({
|
|
115
|
+
isLoaded,
|
|
116
|
+
isSignedIn: user !== null,
|
|
117
|
+
user,
|
|
118
|
+
sessionExpiresAt,
|
|
119
|
+
signIn,
|
|
120
|
+
signOut,
|
|
121
|
+
refresh,
|
|
122
|
+
getToken
|
|
123
|
+
}),
|
|
124
|
+
[isLoaded, user, sessionExpiresAt, signIn, signOut, refresh, getToken]
|
|
125
|
+
);
|
|
126
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(IncortaAuthContext.Provider, { value, children });
|
|
127
|
+
}
|
|
128
|
+
function useIncortaAuth() {
|
|
129
|
+
const context = (0, import_react.useContext)(IncortaAuthContext);
|
|
130
|
+
if (!context) {
|
|
131
|
+
throw new Error("[incorta-auth] useIncortaAuth must be used inside <IncortaAuthProvider>.");
|
|
132
|
+
}
|
|
133
|
+
return context;
|
|
134
|
+
}
|
|
135
|
+
var useAuth = useIncortaAuth;
|
|
136
|
+
function useUser() {
|
|
137
|
+
return useIncortaAuth().user;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/react/components.tsx
|
|
141
|
+
var import_react2 = require("react");
|
|
142
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
143
|
+
function SignedIn({ children }) {
|
|
144
|
+
const { isLoaded, isSignedIn } = useIncortaAuth();
|
|
145
|
+
return isLoaded && isSignedIn ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children }) : null;
|
|
146
|
+
}
|
|
147
|
+
function SignedOut({ children }) {
|
|
148
|
+
const { isLoaded, isSignedIn } = useIncortaAuth();
|
|
149
|
+
return isLoaded && !isSignedIn ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children }) : null;
|
|
150
|
+
}
|
|
151
|
+
function RedirectToLogin({ redirectTo }) {
|
|
152
|
+
const { signIn } = useIncortaAuth();
|
|
153
|
+
(0, import_react2.useEffect)(() => {
|
|
154
|
+
signIn(redirectTo ? { redirectTo } : void 0);
|
|
155
|
+
}, [signIn, redirectTo]);
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
function Protect({
|
|
159
|
+
children,
|
|
160
|
+
fallback,
|
|
161
|
+
loading = null
|
|
162
|
+
}) {
|
|
163
|
+
const { isLoaded, isSignedIn } = useIncortaAuth();
|
|
164
|
+
if (!isLoaded) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: loading });
|
|
165
|
+
if (!isSignedIn) return fallback !== void 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RedirectToLogin, {});
|
|
166
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
167
|
+
}
|
|
168
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
169
|
+
0 && (module.exports = {
|
|
170
|
+
IncortaAuthProvider,
|
|
171
|
+
Protect,
|
|
172
|
+
RedirectToLogin,
|
|
173
|
+
SignedIn,
|
|
174
|
+
SignedOut,
|
|
175
|
+
useAuth,
|
|
176
|
+
useIncortaAuth,
|
|
177
|
+
useUser
|
|
178
|
+
});
|
|
179
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/index.ts","../../src/react/context.tsx","../../src/react/components.tsx"],"sourcesContent":["export {\n IncortaAuthProvider,\n useAuth,\n useIncortaAuth,\n useUser,\n} from \"./context.js\";\nexport type {\n IncortaAuthContextValue,\n IncortaAuthProviderProps,\n SignInOptions,\n SignOutOptions,\n} from \"./context.js\";\nexport { Protect, RedirectToLogin, SignedIn, SignedOut } from \"./components.js\";\nexport type { IncortaUser } from \"../core/config.js\";\n","import {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport type { IncortaUser } from \"../core/config.js\";\n\nexport interface SignInOptions {\n /** Where to land after login. Defaults to the current URL. */\n redirectTo?: string;\n}\n\nexport interface SignOutOptions {\n /** Navigate here after the session is cleared. Defaults to staying put. */\n redirectTo?: string;\n}\n\nexport interface IncortaAuthContextValue {\n /** False until the first session check completes. */\n isLoaded: boolean;\n isSignedIn: boolean;\n user: IncortaUser | null;\n /** Epoch millis when the session expires, when signed in. */\n sessionExpiresAt: number | null;\n /** Sends the browser through Incorta login and back. */\n signIn: (options?: SignInOptions) => void;\n signOut: (options?: SignOutOptions) => Promise<void>;\n /** Re-checks the session against the backend. */\n refresh: () => Promise<void>;\n /**\n * Fetches the raw Incorta access token (requires `exposeAccessToken: true`\n * on the server). Returns null when signed out or not exposed.\n */\n getToken: () => Promise<string | null>;\n}\n\nconst IncortaAuthContext = createContext<IncortaAuthContextValue | null>(null);\n\ninterface SessionResponse {\n user: IncortaUser | null;\n session?: { expiresAt: number; accessTokenExpiresAt: number };\n}\n\nexport interface IncortaAuthProviderProps {\n children: ReactNode;\n /** Must match the backend's basePath. @default \"/auth\" */\n basePath?: string;\n /**\n * Re-check the session every N milliseconds so long-lived tabs keep their\n * Incorta access token fresh (the backend refreshes it server-side).\n * Set 0 to disable. @default 300000 (5 minutes)\n */\n refetchInterval?: number;\n}\n\nexport function IncortaAuthProvider({\n children,\n basePath = \"/auth\",\n refetchInterval = 5 * 60 * 1000,\n}: IncortaAuthProviderProps) {\n const [isLoaded, setIsLoaded] = useState(false);\n const [user, setUser] = useState<IncortaUser | null>(null);\n const [sessionExpiresAt, setSessionExpiresAt] = useState<number | null>(null);\n const mounted = useRef(true);\n\n const refresh = useCallback(async () => {\n try {\n const response = await fetch(`${basePath}/session`, {\n headers: { accept: \"application/json\" },\n });\n const body = (await response.json()) as SessionResponse;\n if (!mounted.current) return;\n setUser(body.user ?? null);\n setSessionExpiresAt(body.session?.expiresAt ?? null);\n } catch {\n if (!mounted.current) return;\n setUser(null);\n setSessionExpiresAt(null);\n } finally {\n if (mounted.current) setIsLoaded(true);\n }\n }, [basePath]);\n\n useEffect(() => {\n mounted.current = true;\n void refresh();\n return () => {\n mounted.current = false;\n };\n }, [refresh]);\n\n useEffect(() => {\n if (!refetchInterval) return;\n const id = setInterval(() => void refresh(), refetchInterval);\n return () => clearInterval(id);\n }, [refetchInterval, refresh]);\n\n const signIn = useCallback(\n (options?: SignInOptions) => {\n if (typeof window === \"undefined\") return;\n const current = `${window.location.pathname}${window.location.search}${window.location.hash}`;\n const redirectTo = options?.redirectTo ?? current;\n window.location.assign(\n `${basePath}/login?redirect_to=${encodeURIComponent(redirectTo)}`,\n );\n },\n [basePath],\n );\n\n const signOut = useCallback(\n async (options?: SignOutOptions) => {\n try {\n await fetch(`${basePath}/logout`, { method: \"POST\" });\n } finally {\n setUser(null);\n setSessionExpiresAt(null);\n if (options?.redirectTo && typeof window !== \"undefined\") {\n window.location.assign(options.redirectTo);\n }\n }\n },\n [basePath],\n );\n\n const getToken = useCallback(async () => {\n try {\n const response = await fetch(`${basePath}/token`, {\n headers: { accept: \"application/json\" },\n });\n if (!response.ok) return null;\n const body = (await response.json()) as { accessToken?: string };\n return body.accessToken ?? null;\n } catch {\n return null;\n }\n }, [basePath]);\n\n const value = useMemo<IncortaAuthContextValue>(\n () => ({\n isLoaded,\n isSignedIn: user !== null,\n user,\n sessionExpiresAt,\n signIn,\n signOut,\n refresh,\n getToken,\n }),\n [isLoaded, user, sessionExpiresAt, signIn, signOut, refresh, getToken],\n );\n\n return <IncortaAuthContext.Provider value={value}>{children}</IncortaAuthContext.Provider>;\n}\n\nexport function useIncortaAuth(): IncortaAuthContextValue {\n const context = useContext(IncortaAuthContext);\n if (!context) {\n throw new Error(\"[incorta-auth] useIncortaAuth must be used inside <IncortaAuthProvider>.\");\n }\n return context;\n}\n\n/** Clerk-style alias of {@link useIncortaAuth}. */\nexport const useAuth = useIncortaAuth;\n\n/** The signed-in user, or null. */\nexport function useUser(): IncortaUser | null {\n return useIncortaAuth().user;\n}\n","import { useEffect, type ReactNode } from \"react\";\nimport { useIncortaAuth } from \"./context.js\";\n\n/** Renders children only when the user is signed in (after the initial load). */\nexport function SignedIn({ children }: { children: ReactNode }) {\n const { isLoaded, isSignedIn } = useIncortaAuth();\n return isLoaded && isSignedIn ? <>{children}</> : null;\n}\n\n/** Renders children only when the user is signed out (after the initial load). */\nexport function SignedOut({ children }: { children: ReactNode }) {\n const { isLoaded, isSignedIn } = useIncortaAuth();\n return isLoaded && !isSignedIn ? <>{children}</> : null;\n}\n\n/**\n * Immediately sends the browser through Incorta login. Render it for signed-out\n * users on protected pages:\n *\n * ```tsx\n * <SignedOut><RedirectToLogin /></SignedOut>\n * ```\n */\nexport function RedirectToLogin({ redirectTo }: { redirectTo?: string }) {\n const { signIn } = useIncortaAuth();\n useEffect(() => {\n signIn(redirectTo ? { redirectTo } : undefined);\n }, [signIn, redirectTo]);\n return null;\n}\n\n/**\n * Gates children behind authentication: shows `loading` during the initial\n * check, redirects signed-out users through Incorta login (or renders\n * `fallback` when provided).\n */\nexport function Protect({\n children,\n fallback,\n loading = null,\n}: {\n children: ReactNode;\n /** Rendered for signed-out users instead of redirecting. */\n fallback?: ReactNode;\n loading?: ReactNode;\n}) {\n const { isLoaded, isSignedIn } = useIncortaAuth();\n if (!isLoaded) return <>{loading}</>;\n if (!isSignedIn) return fallback !== undefined ? <>{fallback}</> : <RedirectToLogin />;\n return <>{children}</>;\n}\n"],"mappings":"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","names":["import_react","import_jsx_runtime"]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { a as IncortaUser } from '../config-B-2cbZDV.cjs';
|
|
4
|
+
import 'jose';
|
|
5
|
+
|
|
6
|
+
interface SignInOptions {
|
|
7
|
+
/** Where to land after login. Defaults to the current URL. */
|
|
8
|
+
redirectTo?: string;
|
|
9
|
+
}
|
|
10
|
+
interface SignOutOptions {
|
|
11
|
+
/** Navigate here after the session is cleared. Defaults to staying put. */
|
|
12
|
+
redirectTo?: string;
|
|
13
|
+
}
|
|
14
|
+
interface IncortaAuthContextValue {
|
|
15
|
+
/** False until the first session check completes. */
|
|
16
|
+
isLoaded: boolean;
|
|
17
|
+
isSignedIn: boolean;
|
|
18
|
+
user: IncortaUser | null;
|
|
19
|
+
/** Epoch millis when the session expires, when signed in. */
|
|
20
|
+
sessionExpiresAt: number | null;
|
|
21
|
+
/** Sends the browser through Incorta login and back. */
|
|
22
|
+
signIn: (options?: SignInOptions) => void;
|
|
23
|
+
signOut: (options?: SignOutOptions) => Promise<void>;
|
|
24
|
+
/** Re-checks the session against the backend. */
|
|
25
|
+
refresh: () => Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Fetches the raw Incorta access token (requires `exposeAccessToken: true`
|
|
28
|
+
* on the server). Returns null when signed out or not exposed.
|
|
29
|
+
*/
|
|
30
|
+
getToken: () => Promise<string | null>;
|
|
31
|
+
}
|
|
32
|
+
interface IncortaAuthProviderProps {
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
/** Must match the backend's basePath. @default "/auth" */
|
|
35
|
+
basePath?: string;
|
|
36
|
+
/**
|
|
37
|
+
* Re-check the session every N milliseconds so long-lived tabs keep their
|
|
38
|
+
* Incorta access token fresh (the backend refreshes it server-side).
|
|
39
|
+
* Set 0 to disable. @default 300000 (5 minutes)
|
|
40
|
+
*/
|
|
41
|
+
refetchInterval?: number;
|
|
42
|
+
}
|
|
43
|
+
declare function IncortaAuthProvider({ children, basePath, refetchInterval, }: IncortaAuthProviderProps): react.JSX.Element;
|
|
44
|
+
declare function useIncortaAuth(): IncortaAuthContextValue;
|
|
45
|
+
/** Clerk-style alias of {@link useIncortaAuth}. */
|
|
46
|
+
declare const useAuth: typeof useIncortaAuth;
|
|
47
|
+
/** The signed-in user, or null. */
|
|
48
|
+
declare function useUser(): IncortaUser | null;
|
|
49
|
+
|
|
50
|
+
/** Renders children only when the user is signed in (after the initial load). */
|
|
51
|
+
declare function SignedIn({ children }: {
|
|
52
|
+
children: ReactNode;
|
|
53
|
+
}): react.JSX.Element | null;
|
|
54
|
+
/** Renders children only when the user is signed out (after the initial load). */
|
|
55
|
+
declare function SignedOut({ children }: {
|
|
56
|
+
children: ReactNode;
|
|
57
|
+
}): react.JSX.Element | null;
|
|
58
|
+
/**
|
|
59
|
+
* Immediately sends the browser through Incorta login. Render it for signed-out
|
|
60
|
+
* users on protected pages:
|
|
61
|
+
*
|
|
62
|
+
* ```tsx
|
|
63
|
+
* <SignedOut><RedirectToLogin /></SignedOut>
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
declare function RedirectToLogin({ redirectTo }: {
|
|
67
|
+
redirectTo?: string;
|
|
68
|
+
}): null;
|
|
69
|
+
/**
|
|
70
|
+
* Gates children behind authentication: shows `loading` during the initial
|
|
71
|
+
* check, redirects signed-out users through Incorta login (or renders
|
|
72
|
+
* `fallback` when provided).
|
|
73
|
+
*/
|
|
74
|
+
declare function Protect({ children, fallback, loading, }: {
|
|
75
|
+
children: ReactNode;
|
|
76
|
+
/** Rendered for signed-out users instead of redirecting. */
|
|
77
|
+
fallback?: ReactNode;
|
|
78
|
+
loading?: ReactNode;
|
|
79
|
+
}): react.JSX.Element;
|
|
80
|
+
|
|
81
|
+
export { type IncortaAuthContextValue, IncortaAuthProvider, type IncortaAuthProviderProps, IncortaUser, Protect, RedirectToLogin, type SignInOptions, type SignOutOptions, SignedIn, SignedOut, useAuth, useIncortaAuth, useUser };
|