@incorta/auth 1.1.0 → 1.2.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/README.md CHANGED
@@ -8,7 +8,7 @@ guide; this is the API surface.
8
8
 
9
9
  | Import | What's in it |
10
10
  | ----------------------- | ------------------------------------------------------------------- |
11
- | `@incorta/auth` | `createIncortaAuth`, `registerClient`, `getClient`, `updateClientRedirectUris`, types (`IncortaUser`, `AuthSession`, …) |
11
+ | `@incorta/auth` | `createIncortaAuth`, `registerClient`, `getClient`, `updateClientRedirectUris`, `deleteClient`, types (`IncortaUser`, `AuthSession`, …) |
12
12
  | `@incorta/auth/react` | `IncortaAuthProvider`, `useAuth`, `useUser`, `SignedIn`, `SignedOut`, `Protect`, `RedirectToLogin` |
13
13
  | `@incorta/auth/express` | `incortaAuth(auth)` middleware, `requireAuth(auth, options?)` |
14
14
  | `@incorta/auth/node` | `toNodeHandler(auth)`, `toWebRequest`, `sendWebResponse`, `getNodeSession` |
@@ -35,7 +35,11 @@ Returns `{ handler, getSession, config }`:
35
35
  for server code (`user`, `accessToken`, `accessTokenExpiresAt`, `expiresAt`).
36
36
 
37
37
  Config falls back to `INCORTA_URL`, `INCORTA_TENANT`, `INCORTA_CLIENT_ID`,
38
- `INCORTA_CLIENT_SECRET`, `INCORTA_AUTH_SECRET`, `INCORTA_APP_URL`.
38
+ `INCORTA_CLIENT_SECRET`, `INCORTA_AUTH_SECRET`, `INCORTA_APP_URL`, and
39
+ `INCORTA_INTERNAL_URL` (split horizon: server-side discovery/token/JWKS calls
40
+ use it when the public `incortaUrl` is not routable from the app — e.g.
41
+ `http://host.docker.internal:8080/incorta` from a local container; the
42
+ authorize redirect and issuer validation stay on the public URL).
39
43
 
40
44
  ## Session model
41
45
 
@@ -54,12 +58,16 @@ incorta-auth show --url ... --tenant ... --client <id> --rat <token> [--json]
54
58
 
55
59
  incorta-auth update-redirects --url ... --tenant ... --client <id> --rat <token> \
56
60
  --redirect <url> [--redirect ...] [--replace] [--json]
61
+
62
+ incorta-auth delete --url ... --tenant ... --client <id> --rat <token>
57
63
  ```
58
64
 
59
65
  `register` creates the OAuth client via RFC 7591 dynamic registration and
60
66
  prints the `.env` entries (keep the registration access token). `show` and
61
67
  `update-redirects` manage the registered client's allowed callback URLs —
62
- adding by default, replacing the whole set with `--replace`.
68
+ adding by default, replacing the whole set with `--replace`. `delete` removes
69
+ the registration (new logins stop immediately; issued sessions live until
70
+ they expire).
63
71
 
64
72
  ## Testing
65
73
 
package/dist/cli.js CHANGED
@@ -118,6 +118,19 @@ async function updateClientRedirectUris(options) {
118
118
  }
119
119
  return getClient(options);
120
120
  }
121
+ async function deleteClient(options) {
122
+ const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;
123
+ const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {
124
+ method: "DELETE",
125
+ headers: {
126
+ accept: "application/json",
127
+ authorization: `Bearer ${registrationAccessToken}`
128
+ }
129
+ });
130
+ if (!response.ok) {
131
+ await readJson(response, "Deleting the client");
132
+ }
133
+ }
121
134
 
122
135
  // src/cli.ts
123
136
  var HELP = `incorta-auth \u2014 IncortaAuth SDK helper
@@ -135,14 +148,19 @@ Usage:
135
148
  --redirect <callbackUrl> [--redirect ...] \\
136
149
  [--replace] [--json]
137
150
 
151
+ incorta-auth delete --url <incortaUrl> --tenant <tenant> \\
152
+ --client <clientId> --rat <registrationAccessToken>
153
+
138
154
  register Registers an OAuth client in Incorta (RFC 7591 dynamic
139
155
  registration) and prints the .env entries your app needs.
140
156
  Keep the printed registration access token \u2014 it is the key
141
- for show/update-redirects later.
157
+ for show/update-redirects/delete later.
142
158
  show Prints a registered client's current metadata.
143
159
  update-redirects Adds the given callback URLs to the client's allowed set
144
160
  (or replaces the whole set with --replace, which is also how
145
161
  URLs are removed).
162
+ delete Deletes the client registration. New logins stop
163
+ immediately; sessions already issued live until they expire.
146
164
 
147
165
  Example:
148
166
  incorta-auth register \\
@@ -263,6 +281,15 @@ async function runUpdateRedirects(args) {
263
281
  console.log(`Redirect URIs ${args.replace ? "replaced" : "updated"}. Current set:`);
264
282
  for (const uri of info.redirectUris) console.log(` - ${uri}`);
265
283
  }
284
+ async function runDelete(args) {
285
+ await deleteClient({
286
+ incortaUrl: args.url,
287
+ tenant: args.tenant,
288
+ clientId: args.client,
289
+ registrationAccessToken: args.rat
290
+ });
291
+ console.log(`Client ${args.client} deleted.`);
292
+ }
266
293
  var COMMANDS = {
267
294
  register: { required: ["url", "tenant", "name"], needsRedirect: true, run: runRegister },
268
295
  show: { required: ["url", "tenant", "client", "rat"], needsRedirect: false, run: runShow },
@@ -270,7 +297,8 @@ var COMMANDS = {
270
297
  required: ["url", "tenant", "client", "rat"],
271
298
  needsRedirect: true,
272
299
  run: runUpdateRedirects
273
- }
300
+ },
301
+ delete: { required: ["url", "tenant", "client", "rat"], needsRedirect: false, run: runDelete }
274
302
  };
275
303
  async function main() {
276
304
  const [command, ...rest] = process.argv.slice(2);
@@ -36,6 +36,16 @@ interface IncortaAuthConfig {
36
36
  * or `http://localhost:8080/incorta`. Defaults to `INCORTA_URL`.
37
37
  */
38
38
  incortaUrl?: string;
39
+ /**
40
+ * Split-horizon override: base URL this SERVER should use to reach Incorta
41
+ * when the public `incortaUrl` is not routable from where the app runs
42
+ * (containers, cluster-internal DNS) — e.g.
43
+ * `http://host.docker.internal:8080/incorta` from a local container. Only
44
+ * server-to-server calls (discovery, token exchange, JWKS) use it; browser
45
+ * redirects and token `iss` validation stay on the public `incortaUrl`.
46
+ * Defaults to `INCORTA_INTERNAL_URL`; unset means no rewrite.
47
+ */
48
+ internalIncortaUrl?: string;
39
49
  /** Incorta tenant name, e.g. `demo`. Defaults to `INCORTA_TENANT`. */
40
50
  tenant?: string;
41
51
  /** OAuth client id (see `incorta-auth register`). Defaults to `INCORTA_CLIENT_ID`. */
@@ -94,6 +104,8 @@ interface IncortaAuthConfig {
94
104
  }
95
105
  interface ResolvedConfig {
96
106
  incortaUrl: string;
107
+ /** Server-side fetch base when set (split horizon) — see the config doc. */
108
+ internalIncortaUrl?: string;
97
109
  tenant: string;
98
110
  clientId: string;
99
111
  clientSecret: string;
@@ -36,6 +36,16 @@ interface IncortaAuthConfig {
36
36
  * or `http://localhost:8080/incorta`. Defaults to `INCORTA_URL`.
37
37
  */
38
38
  incortaUrl?: string;
39
+ /**
40
+ * Split-horizon override: base URL this SERVER should use to reach Incorta
41
+ * when the public `incortaUrl` is not routable from where the app runs
42
+ * (containers, cluster-internal DNS) — e.g.
43
+ * `http://host.docker.internal:8080/incorta` from a local container. Only
44
+ * server-to-server calls (discovery, token exchange, JWKS) use it; browser
45
+ * redirects and token `iss` validation stay on the public `incortaUrl`.
46
+ * Defaults to `INCORTA_INTERNAL_URL`; unset means no rewrite.
47
+ */
48
+ internalIncortaUrl?: string;
39
49
  /** Incorta tenant name, e.g. `demo`. Defaults to `INCORTA_TENANT`. */
40
50
  tenant?: string;
41
51
  /** OAuth client id (see `incorta-auth register`). Defaults to `INCORTA_CLIENT_ID`. */
@@ -94,6 +104,8 @@ interface IncortaAuthConfig {
94
104
  }
95
105
  interface ResolvedConfig {
96
106
  incortaUrl: string;
107
+ /** Server-side fetch base when set (split horizon) — see the config doc. */
108
+ internalIncortaUrl?: string;
97
109
  tenant: string;
98
110
  clientId: string;
99
111
  clientSecret: string;
@@ -1,6 +1,6 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import { AuthSession, IncortaAuth } from './index.cjs';
3
- import './config-B-2cbZDV.cjs';
3
+ import './config-DAKbk8N5.cjs';
4
4
  import 'jose';
5
5
 
6
6
  /**
package/dist/express.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { IncomingMessage, ServerResponse } from 'node:http';
2
2
  import { AuthSession, IncortaAuth } from './index.js';
3
- import './config-B-2cbZDV.js';
3
+ import './config-DAKbk8N5.js';
4
4
  import 'jose';
5
5
 
6
6
  /**
package/dist/index.cjs CHANGED
@@ -22,6 +22,7 @@ var src_exports = {};
22
22
  __export(src_exports, {
23
23
  IncortaAuthError: () => IncortaAuthError,
24
24
  createIncortaAuth: () => createIncortaAuth,
25
+ deleteClient: () => deleteClient,
25
26
  getClient: () => getClient,
26
27
  registerClient: () => registerClient,
27
28
  updateClientRedirectUris: () => updateClientRedirectUris
@@ -45,6 +46,7 @@ function resolveConfig(input = {}) {
45
46
  const incortaUrl = stripTrailingSlash(
46
47
  required(input.incortaUrl ?? env.INCORTA_URL, "incortaUrl", "INCORTA_URL")
47
48
  );
49
+ const internalIncortaUrl = input.internalIncortaUrl ?? env.INCORTA_INTERNAL_URL;
48
50
  const tenant = required(input.tenant ?? env.INCORTA_TENANT, "tenant", "INCORTA_TENANT");
49
51
  const clientId = required(
50
52
  input.clientId ?? env.INCORTA_CLIENT_ID,
@@ -77,6 +79,7 @@ function resolveConfig(input = {}) {
77
79
  const issuer = `${incortaUrl}/oauth/${tenant}`;
78
80
  return {
79
81
  incortaUrl,
82
+ internalIncortaUrl: internalIncortaUrl ? stripTrailingSlash(internalIncortaUrl) : void 0,
80
83
  tenant,
81
84
  clientId,
82
85
  clientSecret,
@@ -138,14 +141,20 @@ function deleteCookie(name, options = {}) {
138
141
  // src/core/discovery.ts
139
142
  var import_jose = require("jose");
140
143
  var endpointsCache = /* @__PURE__ */ new Map();
144
+ function toInternal(config, url) {
145
+ const internal = config.internalIncortaUrl;
146
+ if (!internal || !url.startsWith(config.incortaUrl)) return url;
147
+ return internal + url.slice(config.incortaUrl.length);
148
+ }
141
149
  function getEndpoints(config) {
142
- const cached = endpointsCache.get(config.issuer);
150
+ const cacheKey = `${config.issuer}|${config.internalIncortaUrl ?? ""}`;
151
+ const cached = endpointsCache.get(cacheKey);
143
152
  if (cached) return cached;
144
153
  const promise = resolve(config).catch((error) => {
145
- endpointsCache.delete(config.issuer);
154
+ endpointsCache.delete(cacheKey);
146
155
  throw error;
147
156
  });
148
- endpointsCache.set(config.issuer, promise);
157
+ endpointsCache.set(cacheKey, promise);
149
158
  return promise;
150
159
  }
151
160
  async function resolve(config) {
@@ -157,7 +166,7 @@ async function resolve(config) {
157
166
  };
158
167
  let doc = fallback;
159
168
  try {
160
- const response = await config.fetch(config.discoveryUrl, {
169
+ const response = await config.fetch(toInternal(config, config.discoveryUrl), {
161
170
  headers: { accept: "application/json" }
162
171
  });
163
172
  if (response.ok) {
@@ -171,14 +180,14 @@ async function resolve(config) {
171
180
  }
172
181
  } catch {
173
182
  }
174
- const jwks = config.jwksOverride ?? (0, import_jose.createRemoteJWKSet)(new URL(doc.jwks_uri), {
183
+ const jwks = config.jwksOverride ?? (0, import_jose.createRemoteJWKSet)(new URL(toInternal(config, doc.jwks_uri)), {
175
184
  cacheMaxAge: 10 * 60 * 1e3,
176
185
  cooldownDuration: 30 * 1e3
177
186
  });
178
187
  return {
179
188
  issuer: doc.issuer,
180
189
  authorizationEndpoint: doc.authorization_endpoint,
181
- tokenEndpoint: doc.token_endpoint,
190
+ tokenEndpoint: toInternal(config, doc.token_endpoint),
182
191
  jwks
183
192
  };
184
193
  }
@@ -697,6 +706,19 @@ async function updateClientRedirectUris(options) {
697
706
  }
698
707
  return getClient(options);
699
708
  }
709
+ async function deleteClient(options) {
710
+ const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;
711
+ const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {
712
+ method: "DELETE",
713
+ headers: {
714
+ accept: "application/json",
715
+ authorization: `Bearer ${registrationAccessToken}`
716
+ }
717
+ });
718
+ if (!response.ok) {
719
+ await readJson(response, "Deleting the client");
720
+ }
721
+ }
700
722
 
701
723
  // src/index.ts
702
724
  function createIncortaAuth(config = {}) {
@@ -708,6 +730,7 @@ function createIncortaAuth(config = {}) {
708
730
  0 && (module.exports = {
709
731
  IncortaAuthError,
710
732
  createIncortaAuth,
733
+ deleteClient,
711
734
  getClient,
712
735
  registerClient,
713
736
  updateClientRedirectUris
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../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"],"sourcesContent":["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 { getClient, registerClient, updateClientRedirectUris } from \"./core/register.js\";\nexport type {\n ClientInfo,\n ManageClientOptions,\n RegisterClientOptions,\n RegisteredClient,\n UpdateRedirectUrisOptions,\n} 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","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","interface ManagementErrorBody {\n error?: string;\n error_description?: string;\n}\n\nfunction registrationEndpoint(incortaUrl: string, tenant: string, clientId?: string): string {\n const base = `${incortaUrl.replace(/\\/+$/, \"\")}/oauth/${tenant}/register`;\n return clientId ? `${base}/${clientId}` : base;\n}\n\nasync function readJson<T extends ManagementErrorBody>(\n response: Response,\n what: string,\n): Promise<T> {\n let body: T;\n try {\n body = (await response.json()) as T;\n } catch {\n throw new Error(\n `[incorta-auth] ${what} returned a non-JSON response (HTTP ${response.status}). ` +\n \"Is the OAuth authorization server enabled on this cluster?\",\n );\n }\n if (!response.ok) {\n throw new Error(\n `[incorta-auth] ${what} failed (HTTP ${response.status}): ` +\n `${body.error ?? \"unknown_error\"} — ${body.error_description ?? \"no description\"}`,\n );\n }\n return body;\n}\n\nexport 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 response = await fetchImpl(registrationEndpoint(incortaUrl, tenant), {\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 extends ManagementErrorBody {\n client_id?: string;\n client_secret?: string;\n registration_access_token?: string;\n registration_client_uri?: string;\n }\n\n const body = await readJson<RegistrationResponse>(response, \"Client registration\");\n if (!body.client_id || !body.client_secret) {\n throw new Error(\n `[incorta-auth] Client registration failed: response is missing client_id/client_secret.`,\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\nexport interface ManageClientOptions {\n /** Incorta base URL, e.g. `http://localhost:8080/incorta`. */\n incortaUrl: string;\n tenant: string;\n clientId: string;\n /** The registration access token returned when the client was registered. */\n registrationAccessToken: string;\n fetchImpl?: typeof fetch;\n}\n\n/** Current metadata of a registered client. */\nexport interface ClientInfo {\n clientId: string;\n name?: string;\n redirectUris: string[];\n grantTypes: string[];\n scope?: string;\n}\n\ninterface ClientMetadataResponse extends ManagementErrorBody {\n client_id?: string;\n client_name?: string;\n redirect_uris?: string[];\n grant_types?: string[];\n scope?: string;\n}\n\nfunction toClientInfo(body: ClientMetadataResponse, fallbackClientId: string): ClientInfo {\n return {\n clientId: body.client_id ?? fallbackClientId,\n name: body.client_name,\n redirectUris: body.redirect_uris ?? [],\n grantTypes: body.grant_types ?? [],\n scope: body.scope,\n };\n}\n\n/**\n * Reads a registered client's current metadata (RFC 7592-style management,\n * `GET {incortaUrl}/oauth/{tenant}/register/{clientId}` with the registration\n * access token).\n */\nexport async function getClient(options: ManageClientOptions): Promise<ClientInfo> {\n const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;\n const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${registrationAccessToken}`,\n },\n });\n const body = await readJson<ClientMetadataResponse>(response, \"Reading the client\");\n return toClientInfo(body, clientId);\n}\n\nexport interface UpdateRedirectUrisOptions extends ManageClientOptions {\n redirectUris: string[];\n /**\n * `\"add\"` (default) merges the given URIs into the client's current set;\n * `\"replace\"` makes the given list the exact new set (also how URIs are\n * removed).\n */\n mode?: \"add\" | \"replace\";\n}\n\n/**\n * Updates the allowed OAuth callback URLs of a registered client. Incorta's\n * management endpoint applies `redirect_uris` from the PUT body (other\n * metadata is unchanged server-side).\n */\nexport async function updateClientRedirectUris(\n options: UpdateRedirectUrisOptions,\n): Promise<ClientInfo> {\n const {\n incortaUrl,\n tenant,\n clientId,\n registrationAccessToken,\n redirectUris,\n mode = \"add\",\n fetchImpl = fetch,\n } = options;\n\n if (!redirectUris.length) {\n // An empty replace would leave the client unable to complete the\n // authorization-code flow at all — delete the registration instead.\n throw new Error(\"[incorta-auth] updateClientRedirectUris requires at least one redirect URI.\");\n }\n\n const current = await getClient(options);\n const nextUris =\n mode === \"add\" ? [...new Set([...current.redirectUris, ...redirectUris])] : redirectUris;\n\n const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {\n method: \"PUT\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n authorization: `Bearer ${registrationAccessToken}`,\n },\n // RFC 7592 update: client_id + full metadata. Incorta applies\n // redirect_uris only, but nimbus-side parsing wants the full shape.\n body: JSON.stringify({\n client_id: clientId,\n redirect_uris: nextUris,\n grant_types: current.grantTypes,\n scope: current.scope,\n client_name: current.name,\n }),\n });\n\n if (!response.ok) {\n await readJson<ManagementErrorBody>(response, \"Updating the client\"); // throws with details\n }\n // The PUT response body is empty on success — re-read for the fresh state.\n return getClient(options);\n}\n"],"mappings":"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","names":["import_node_crypto","import_jose","import_jose","code"]}
1
+ {"version":3,"sources":["../src/index.ts","../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"],"sourcesContent":["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 {\n deleteClient,\n getClient,\n registerClient,\n updateClientRedirectUris,\n} from \"./core/register.js\";\nexport type {\n ClientInfo,\n ManageClientOptions,\n RegisterClientOptions,\n RegisteredClient,\n UpdateRedirectUrisOptions,\n} 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","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 /**\n * Split-horizon override: base URL this SERVER should use to reach Incorta\n * when the public `incortaUrl` is not routable from where the app runs\n * (containers, cluster-internal DNS) — e.g.\n * `http://host.docker.internal:8080/incorta` from a local container. Only\n * server-to-server calls (discovery, token exchange, JWKS) use it; browser\n * redirects and token `iss` validation stay on the public `incortaUrl`.\n * Defaults to `INCORTA_INTERNAL_URL`; unset means no rewrite.\n */\n internalIncortaUrl?: 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 /** Server-side fetch base when set (split horizon) — see the config doc. */\n internalIncortaUrl?: 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 internalIncortaUrl = input.internalIncortaUrl ?? env.INCORTA_INTERNAL_URL;\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 internalIncortaUrl: internalIncortaUrl\n ? stripTrailingSlash(internalIncortaUrl)\n : undefined,\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 * Split horizon: URLs the SERVER fetches (discovery, token endpoint, JWKS)\n * are rebased from the public `incortaUrl` onto `internalIncortaUrl` when\n * set. Values the BROWSER navigates to (authorization endpoint) and values\n * used for VALIDATION (issuer — must equal the token's `iss` claim, which\n * Incorta mints from its public URL) are never rewritten. URLs outside the\n * public base pass through untouched.\n */\nfunction toInternal(config: ResolvedConfig, url: string): string {\n const internal = config.internalIncortaUrl;\n if (!internal || !url.startsWith(config.incortaUrl)) return url;\n return internal + url.slice(config.incortaUrl.length);\n}\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 (and internal-URL horizon) for\n * the process lifetime.\n */\nexport function getEndpoints(config: ResolvedConfig): Promise<Endpoints> {\n const cacheKey = `${config.issuer}|${config.internalIncortaUrl ?? \"\"}`;\n const cached = endpointsCache.get(cacheKey);\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(cacheKey);\n throw error;\n });\n endpointsCache.set(cacheKey, 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(toInternal(config, 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(toInternal(config, 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: toInternal(config, 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","interface ManagementErrorBody {\n error?: string;\n error_description?: string;\n}\n\nfunction registrationEndpoint(incortaUrl: string, tenant: string, clientId?: string): string {\n const base = `${incortaUrl.replace(/\\/+$/, \"\")}/oauth/${tenant}/register`;\n return clientId ? `${base}/${clientId}` : base;\n}\n\nasync function readJson<T extends ManagementErrorBody>(\n response: Response,\n what: string,\n): Promise<T> {\n let body: T;\n try {\n body = (await response.json()) as T;\n } catch {\n throw new Error(\n `[incorta-auth] ${what} returned a non-JSON response (HTTP ${response.status}). ` +\n \"Is the OAuth authorization server enabled on this cluster?\",\n );\n }\n if (!response.ok) {\n throw new Error(\n `[incorta-auth] ${what} failed (HTTP ${response.status}): ` +\n `${body.error ?? \"unknown_error\"} — ${body.error_description ?? \"no description\"}`,\n );\n }\n return body;\n}\n\nexport 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 response = await fetchImpl(registrationEndpoint(incortaUrl, tenant), {\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 extends ManagementErrorBody {\n client_id?: string;\n client_secret?: string;\n registration_access_token?: string;\n registration_client_uri?: string;\n }\n\n const body = await readJson<RegistrationResponse>(response, \"Client registration\");\n if (!body.client_id || !body.client_secret) {\n throw new Error(\n `[incorta-auth] Client registration failed: response is missing client_id/client_secret.`,\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\nexport interface ManageClientOptions {\n /** Incorta base URL, e.g. `http://localhost:8080/incorta`. */\n incortaUrl: string;\n tenant: string;\n clientId: string;\n /** The registration access token returned when the client was registered. */\n registrationAccessToken: string;\n fetchImpl?: typeof fetch;\n}\n\n/** Current metadata of a registered client. */\nexport interface ClientInfo {\n clientId: string;\n name?: string;\n redirectUris: string[];\n grantTypes: string[];\n scope?: string;\n}\n\ninterface ClientMetadataResponse extends ManagementErrorBody {\n client_id?: string;\n client_name?: string;\n redirect_uris?: string[];\n grant_types?: string[];\n scope?: string;\n}\n\nfunction toClientInfo(body: ClientMetadataResponse, fallbackClientId: string): ClientInfo {\n return {\n clientId: body.client_id ?? fallbackClientId,\n name: body.client_name,\n redirectUris: body.redirect_uris ?? [],\n grantTypes: body.grant_types ?? [],\n scope: body.scope,\n };\n}\n\n/**\n * Reads a registered client's current metadata (RFC 7592-style management,\n * `GET {incortaUrl}/oauth/{tenant}/register/{clientId}` with the registration\n * access token).\n */\nexport async function getClient(options: ManageClientOptions): Promise<ClientInfo> {\n const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;\n const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${registrationAccessToken}`,\n },\n });\n const body = await readJson<ClientMetadataResponse>(response, \"Reading the client\");\n return toClientInfo(body, clientId);\n}\n\nexport interface UpdateRedirectUrisOptions extends ManageClientOptions {\n redirectUris: string[];\n /**\n * `\"add\"` (default) merges the given URIs into the client's current set;\n * `\"replace\"` makes the given list the exact new set (also how URIs are\n * removed).\n */\n mode?: \"add\" | \"replace\";\n}\n\n/**\n * Updates the allowed OAuth callback URLs of a registered client. Incorta's\n * management endpoint applies `redirect_uris` from the PUT body (other\n * metadata is unchanged server-side).\n */\nexport async function updateClientRedirectUris(\n options: UpdateRedirectUrisOptions,\n): Promise<ClientInfo> {\n const {\n incortaUrl,\n tenant,\n clientId,\n registrationAccessToken,\n redirectUris,\n mode = \"add\",\n fetchImpl = fetch,\n } = options;\n\n if (!redirectUris.length) {\n // An empty replace would leave the client unable to complete the\n // authorization-code flow at all — delete the registration instead.\n throw new Error(\"[incorta-auth] updateClientRedirectUris requires at least one redirect URI.\");\n }\n\n const current = await getClient(options);\n const nextUris =\n mode === \"add\" ? [...new Set([...current.redirectUris, ...redirectUris])] : redirectUris;\n\n const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {\n method: \"PUT\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n authorization: `Bearer ${registrationAccessToken}`,\n },\n // RFC 7592 update: client_id + full metadata. Incorta applies\n // redirect_uris only, but nimbus-side parsing wants the full shape.\n body: JSON.stringify({\n client_id: clientId,\n redirect_uris: nextUris,\n grant_types: current.grantTypes,\n scope: current.scope,\n client_name: current.name,\n }),\n });\n\n if (!response.ok) {\n await readJson<ManagementErrorBody>(response, \"Updating the client\"); // throws with details\n }\n // The PUT response body is empty on success — re-read for the fresh state.\n return getClient(options);\n}\n\n/**\n * Deletes a registered client (RFC 7592-style management,\n * `DELETE {incortaUrl}/oauth/{tenant}/register/{clientId}` with the\n * registration access token). Existing sessions in apps using the client keep\n * working until they expire; new logins and token refreshes stop immediately.\n */\nexport async function deleteClient(options: ManageClientOptions): Promise<void> {\n const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;\n const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {\n method: \"DELETE\",\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${registrationAccessToken}`,\n },\n });\n if (!response.ok) {\n await readJson<ManagementErrorBody>(response, \"Deleting the client\"); // throws with details\n }\n}\n"],"mappings":"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","names":["import_node_crypto","import_jose","import_jose","code"]}
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SessionInfo, R as ResolvedConfig, I as IncortaAuthConfig } from './config-B-2cbZDV.cjs';
2
- export { a as IncortaUser, b as SessionData } from './config-B-2cbZDV.cjs';
1
+ import { S as SessionInfo, R as ResolvedConfig, I as IncortaAuthConfig } from './config-DAKbk8N5.cjs';
2
+ export { a as IncortaUser, b as SessionData } from './config-DAKbk8N5.cjs';
3
3
  import 'jose';
4
4
 
5
5
  /** A read-only view of the current session for server-side app code. */
@@ -83,6 +83,13 @@ interface UpdateRedirectUrisOptions extends ManageClientOptions {
83
83
  * metadata is unchanged server-side).
84
84
  */
85
85
  declare function updateClientRedirectUris(options: UpdateRedirectUrisOptions): Promise<ClientInfo>;
86
+ /**
87
+ * Deletes a registered client (RFC 7592-style management,
88
+ * `DELETE {incortaUrl}/oauth/{tenant}/register/{clientId}` with the
89
+ * registration access token). Existing sessions in apps using the client keep
90
+ * working until they expire; new logins and token refreshes stop immediately.
91
+ */
92
+ declare function deleteClient(options: ManageClientOptions): Promise<void>;
86
93
 
87
94
  interface IncortaAuth {
88
95
  /**
@@ -110,4 +117,4 @@ interface IncortaAuth {
110
117
  */
111
118
  declare function createIncortaAuth(config?: IncortaAuthConfig): IncortaAuth;
112
119
 
113
- export { type AuthHandler, type AuthSession, type ClientInfo, type IncortaAuth, IncortaAuthConfig, IncortaAuthError, type ManageClientOptions, type RegisterClientOptions, type RegisteredClient, SessionInfo, type UpdateRedirectUrisOptions, createIncortaAuth, getClient, registerClient, updateClientRedirectUris };
120
+ export { type AuthHandler, type AuthSession, type ClientInfo, type IncortaAuth, IncortaAuthConfig, IncortaAuthError, type ManageClientOptions, type RegisterClientOptions, type RegisteredClient, SessionInfo, type UpdateRedirectUrisOptions, createIncortaAuth, deleteClient, getClient, registerClient, updateClientRedirectUris };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { S as SessionInfo, R as ResolvedConfig, I as IncortaAuthConfig } from './config-B-2cbZDV.js';
2
- export { a as IncortaUser, b as SessionData } from './config-B-2cbZDV.js';
1
+ import { S as SessionInfo, R as ResolvedConfig, I as IncortaAuthConfig } from './config-DAKbk8N5.js';
2
+ export { a as IncortaUser, b as SessionData } from './config-DAKbk8N5.js';
3
3
  import 'jose';
4
4
 
5
5
  /** A read-only view of the current session for server-side app code. */
@@ -83,6 +83,13 @@ interface UpdateRedirectUrisOptions extends ManageClientOptions {
83
83
  * metadata is unchanged server-side).
84
84
  */
85
85
  declare function updateClientRedirectUris(options: UpdateRedirectUrisOptions): Promise<ClientInfo>;
86
+ /**
87
+ * Deletes a registered client (RFC 7592-style management,
88
+ * `DELETE {incortaUrl}/oauth/{tenant}/register/{clientId}` with the
89
+ * registration access token). Existing sessions in apps using the client keep
90
+ * working until they expire; new logins and token refreshes stop immediately.
91
+ */
92
+ declare function deleteClient(options: ManageClientOptions): Promise<void>;
86
93
 
87
94
  interface IncortaAuth {
88
95
  /**
@@ -110,4 +117,4 @@ interface IncortaAuth {
110
117
  */
111
118
  declare function createIncortaAuth(config?: IncortaAuthConfig): IncortaAuth;
112
119
 
113
- export { type AuthHandler, type AuthSession, type ClientInfo, type IncortaAuth, IncortaAuthConfig, IncortaAuthError, type ManageClientOptions, type RegisterClientOptions, type RegisteredClient, SessionInfo, type UpdateRedirectUrisOptions, createIncortaAuth, getClient, registerClient, updateClientRedirectUris };
120
+ export { type AuthHandler, type AuthSession, type ClientInfo, type IncortaAuth, IncortaAuthConfig, IncortaAuthError, type ManageClientOptions, type RegisterClientOptions, type RegisteredClient, SessionInfo, type UpdateRedirectUrisOptions, createIncortaAuth, deleteClient, getClient, registerClient, updateClientRedirectUris };
package/dist/index.js CHANGED
@@ -15,6 +15,7 @@ function resolveConfig(input = {}) {
15
15
  const incortaUrl = stripTrailingSlash(
16
16
  required(input.incortaUrl ?? env.INCORTA_URL, "incortaUrl", "INCORTA_URL")
17
17
  );
18
+ const internalIncortaUrl = input.internalIncortaUrl ?? env.INCORTA_INTERNAL_URL;
18
19
  const tenant = required(input.tenant ?? env.INCORTA_TENANT, "tenant", "INCORTA_TENANT");
19
20
  const clientId = required(
20
21
  input.clientId ?? env.INCORTA_CLIENT_ID,
@@ -47,6 +48,7 @@ function resolveConfig(input = {}) {
47
48
  const issuer = `${incortaUrl}/oauth/${tenant}`;
48
49
  return {
49
50
  incortaUrl,
51
+ internalIncortaUrl: internalIncortaUrl ? stripTrailingSlash(internalIncortaUrl) : void 0,
50
52
  tenant,
51
53
  clientId,
52
54
  clientSecret,
@@ -108,14 +110,20 @@ function deleteCookie(name, options = {}) {
108
110
  // src/core/discovery.ts
109
111
  import { createRemoteJWKSet } from "jose";
110
112
  var endpointsCache = /* @__PURE__ */ new Map();
113
+ function toInternal(config, url) {
114
+ const internal = config.internalIncortaUrl;
115
+ if (!internal || !url.startsWith(config.incortaUrl)) return url;
116
+ return internal + url.slice(config.incortaUrl.length);
117
+ }
111
118
  function getEndpoints(config) {
112
- const cached = endpointsCache.get(config.issuer);
119
+ const cacheKey = `${config.issuer}|${config.internalIncortaUrl ?? ""}`;
120
+ const cached = endpointsCache.get(cacheKey);
113
121
  if (cached) return cached;
114
122
  const promise = resolve(config).catch((error) => {
115
- endpointsCache.delete(config.issuer);
123
+ endpointsCache.delete(cacheKey);
116
124
  throw error;
117
125
  });
118
- endpointsCache.set(config.issuer, promise);
126
+ endpointsCache.set(cacheKey, promise);
119
127
  return promise;
120
128
  }
121
129
  async function resolve(config) {
@@ -127,7 +135,7 @@ async function resolve(config) {
127
135
  };
128
136
  let doc = fallback;
129
137
  try {
130
- const response = await config.fetch(config.discoveryUrl, {
138
+ const response = await config.fetch(toInternal(config, config.discoveryUrl), {
131
139
  headers: { accept: "application/json" }
132
140
  });
133
141
  if (response.ok) {
@@ -141,14 +149,14 @@ async function resolve(config) {
141
149
  }
142
150
  } catch {
143
151
  }
144
- const jwks = config.jwksOverride ?? createRemoteJWKSet(new URL(doc.jwks_uri), {
152
+ const jwks = config.jwksOverride ?? createRemoteJWKSet(new URL(toInternal(config, doc.jwks_uri)), {
145
153
  cacheMaxAge: 10 * 60 * 1e3,
146
154
  cooldownDuration: 30 * 1e3
147
155
  });
148
156
  return {
149
157
  issuer: doc.issuer,
150
158
  authorizationEndpoint: doc.authorization_endpoint,
151
- tokenEndpoint: doc.token_endpoint,
159
+ tokenEndpoint: toInternal(config, doc.token_endpoint),
152
160
  jwks
153
161
  };
154
162
  }
@@ -667,6 +675,19 @@ async function updateClientRedirectUris(options) {
667
675
  }
668
676
  return getClient(options);
669
677
  }
678
+ async function deleteClient(options) {
679
+ const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;
680
+ const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {
681
+ method: "DELETE",
682
+ headers: {
683
+ accept: "application/json",
684
+ authorization: `Bearer ${registrationAccessToken}`
685
+ }
686
+ });
687
+ if (!response.ok) {
688
+ await readJson(response, "Deleting the client");
689
+ }
690
+ }
670
691
 
671
692
  // src/index.ts
672
693
  function createIncortaAuth(config = {}) {
@@ -677,6 +698,7 @@ function createIncortaAuth(config = {}) {
677
698
  export {
678
699
  IncortaAuthError,
679
700
  createIncortaAuth,
701
+ deleteClient,
680
702
  getClient,
681
703
  registerClient,
682
704
  updateClientRedirectUris
package/dist/index.js.map CHANGED
@@ -1 +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","interface ManagementErrorBody {\n error?: string;\n error_description?: string;\n}\n\nfunction registrationEndpoint(incortaUrl: string, tenant: string, clientId?: string): string {\n const base = `${incortaUrl.replace(/\\/+$/, \"\")}/oauth/${tenant}/register`;\n return clientId ? `${base}/${clientId}` : base;\n}\n\nasync function readJson<T extends ManagementErrorBody>(\n response: Response,\n what: string,\n): Promise<T> {\n let body: T;\n try {\n body = (await response.json()) as T;\n } catch {\n throw new Error(\n `[incorta-auth] ${what} returned a non-JSON response (HTTP ${response.status}). ` +\n \"Is the OAuth authorization server enabled on this cluster?\",\n );\n }\n if (!response.ok) {\n throw new Error(\n `[incorta-auth] ${what} failed (HTTP ${response.status}): ` +\n `${body.error ?? \"unknown_error\"} — ${body.error_description ?? \"no description\"}`,\n );\n }\n return body;\n}\n\nexport 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 response = await fetchImpl(registrationEndpoint(incortaUrl, tenant), {\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 extends ManagementErrorBody {\n client_id?: string;\n client_secret?: string;\n registration_access_token?: string;\n registration_client_uri?: string;\n }\n\n const body = await readJson<RegistrationResponse>(response, \"Client registration\");\n if (!body.client_id || !body.client_secret) {\n throw new Error(\n `[incorta-auth] Client registration failed: response is missing client_id/client_secret.`,\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\nexport interface ManageClientOptions {\n /** Incorta base URL, e.g. `http://localhost:8080/incorta`. */\n incortaUrl: string;\n tenant: string;\n clientId: string;\n /** The registration access token returned when the client was registered. */\n registrationAccessToken: string;\n fetchImpl?: typeof fetch;\n}\n\n/** Current metadata of a registered client. */\nexport interface ClientInfo {\n clientId: string;\n name?: string;\n redirectUris: string[];\n grantTypes: string[];\n scope?: string;\n}\n\ninterface ClientMetadataResponse extends ManagementErrorBody {\n client_id?: string;\n client_name?: string;\n redirect_uris?: string[];\n grant_types?: string[];\n scope?: string;\n}\n\nfunction toClientInfo(body: ClientMetadataResponse, fallbackClientId: string): ClientInfo {\n return {\n clientId: body.client_id ?? fallbackClientId,\n name: body.client_name,\n redirectUris: body.redirect_uris ?? [],\n grantTypes: body.grant_types ?? [],\n scope: body.scope,\n };\n}\n\n/**\n * Reads a registered client's current metadata (RFC 7592-style management,\n * `GET {incortaUrl}/oauth/{tenant}/register/{clientId}` with the registration\n * access token).\n */\nexport async function getClient(options: ManageClientOptions): Promise<ClientInfo> {\n const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;\n const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${registrationAccessToken}`,\n },\n });\n const body = await readJson<ClientMetadataResponse>(response, \"Reading the client\");\n return toClientInfo(body, clientId);\n}\n\nexport interface UpdateRedirectUrisOptions extends ManageClientOptions {\n redirectUris: string[];\n /**\n * `\"add\"` (default) merges the given URIs into the client's current set;\n * `\"replace\"` makes the given list the exact new set (also how URIs are\n * removed).\n */\n mode?: \"add\" | \"replace\";\n}\n\n/**\n * Updates the allowed OAuth callback URLs of a registered client. Incorta's\n * management endpoint applies `redirect_uris` from the PUT body (other\n * metadata is unchanged server-side).\n */\nexport async function updateClientRedirectUris(\n options: UpdateRedirectUrisOptions,\n): Promise<ClientInfo> {\n const {\n incortaUrl,\n tenant,\n clientId,\n registrationAccessToken,\n redirectUris,\n mode = \"add\",\n fetchImpl = fetch,\n } = options;\n\n if (!redirectUris.length) {\n // An empty replace would leave the client unable to complete the\n // authorization-code flow at all — delete the registration instead.\n throw new Error(\"[incorta-auth] updateClientRedirectUris requires at least one redirect URI.\");\n }\n\n const current = await getClient(options);\n const nextUris =\n mode === \"add\" ? [...new Set([...current.redirectUris, ...redirectUris])] : redirectUris;\n\n const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {\n method: \"PUT\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n authorization: `Bearer ${registrationAccessToken}`,\n },\n // RFC 7592 update: client_id + full metadata. Incorta applies\n // redirect_uris only, but nimbus-side parsing wants the full shape.\n body: JSON.stringify({\n client_id: clientId,\n redirect_uris: nextUris,\n grant_types: current.grantTypes,\n scope: current.scope,\n client_name: current.name,\n }),\n });\n\n if (!response.ok) {\n await readJson<ManagementErrorBody>(response, \"Updating the client\"); // throws with details\n }\n // The PUT response body is empty on success — re-read for the fresh state.\n return getClient(options);\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 { getClient, registerClient, updateClientRedirectUris } from \"./core/register.js\";\nexport type {\n ClientInfo,\n ManageClientOptions,\n RegisterClientOptions,\n RegisteredClient,\n UpdateRedirectUrisOptions,\n} 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"]}
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 /**\n * Split-horizon override: base URL this SERVER should use to reach Incorta\n * when the public `incortaUrl` is not routable from where the app runs\n * (containers, cluster-internal DNS) — e.g.\n * `http://host.docker.internal:8080/incorta` from a local container. Only\n * server-to-server calls (discovery, token exchange, JWKS) use it; browser\n * redirects and token `iss` validation stay on the public `incortaUrl`.\n * Defaults to `INCORTA_INTERNAL_URL`; unset means no rewrite.\n */\n internalIncortaUrl?: 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 /** Server-side fetch base when set (split horizon) — see the config doc. */\n internalIncortaUrl?: 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 internalIncortaUrl = input.internalIncortaUrl ?? env.INCORTA_INTERNAL_URL;\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 internalIncortaUrl: internalIncortaUrl\n ? stripTrailingSlash(internalIncortaUrl)\n : undefined,\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 * Split horizon: URLs the SERVER fetches (discovery, token endpoint, JWKS)\n * are rebased from the public `incortaUrl` onto `internalIncortaUrl` when\n * set. Values the BROWSER navigates to (authorization endpoint) and values\n * used for VALIDATION (issuer — must equal the token's `iss` claim, which\n * Incorta mints from its public URL) are never rewritten. URLs outside the\n * public base pass through untouched.\n */\nfunction toInternal(config: ResolvedConfig, url: string): string {\n const internal = config.internalIncortaUrl;\n if (!internal || !url.startsWith(config.incortaUrl)) return url;\n return internal + url.slice(config.incortaUrl.length);\n}\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 (and internal-URL horizon) for\n * the process lifetime.\n */\nexport function getEndpoints(config: ResolvedConfig): Promise<Endpoints> {\n const cacheKey = `${config.issuer}|${config.internalIncortaUrl ?? \"\"}`;\n const cached = endpointsCache.get(cacheKey);\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(cacheKey);\n throw error;\n });\n endpointsCache.set(cacheKey, 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(toInternal(config, 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(toInternal(config, 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: toInternal(config, 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","interface ManagementErrorBody {\n error?: string;\n error_description?: string;\n}\n\nfunction registrationEndpoint(incortaUrl: string, tenant: string, clientId?: string): string {\n const base = `${incortaUrl.replace(/\\/+$/, \"\")}/oauth/${tenant}/register`;\n return clientId ? `${base}/${clientId}` : base;\n}\n\nasync function readJson<T extends ManagementErrorBody>(\n response: Response,\n what: string,\n): Promise<T> {\n let body: T;\n try {\n body = (await response.json()) as T;\n } catch {\n throw new Error(\n `[incorta-auth] ${what} returned a non-JSON response (HTTP ${response.status}). ` +\n \"Is the OAuth authorization server enabled on this cluster?\",\n );\n }\n if (!response.ok) {\n throw new Error(\n `[incorta-auth] ${what} failed (HTTP ${response.status}): ` +\n `${body.error ?? \"unknown_error\"} — ${body.error_description ?? \"no description\"}`,\n );\n }\n return body;\n}\n\nexport 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 response = await fetchImpl(registrationEndpoint(incortaUrl, tenant), {\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 extends ManagementErrorBody {\n client_id?: string;\n client_secret?: string;\n registration_access_token?: string;\n registration_client_uri?: string;\n }\n\n const body = await readJson<RegistrationResponse>(response, \"Client registration\");\n if (!body.client_id || !body.client_secret) {\n throw new Error(\n `[incorta-auth] Client registration failed: response is missing client_id/client_secret.`,\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\nexport interface ManageClientOptions {\n /** Incorta base URL, e.g. `http://localhost:8080/incorta`. */\n incortaUrl: string;\n tenant: string;\n clientId: string;\n /** The registration access token returned when the client was registered. */\n registrationAccessToken: string;\n fetchImpl?: typeof fetch;\n}\n\n/** Current metadata of a registered client. */\nexport interface ClientInfo {\n clientId: string;\n name?: string;\n redirectUris: string[];\n grantTypes: string[];\n scope?: string;\n}\n\ninterface ClientMetadataResponse extends ManagementErrorBody {\n client_id?: string;\n client_name?: string;\n redirect_uris?: string[];\n grant_types?: string[];\n scope?: string;\n}\n\nfunction toClientInfo(body: ClientMetadataResponse, fallbackClientId: string): ClientInfo {\n return {\n clientId: body.client_id ?? fallbackClientId,\n name: body.client_name,\n redirectUris: body.redirect_uris ?? [],\n grantTypes: body.grant_types ?? [],\n scope: body.scope,\n };\n}\n\n/**\n * Reads a registered client's current metadata (RFC 7592-style management,\n * `GET {incortaUrl}/oauth/{tenant}/register/{clientId}` with the registration\n * access token).\n */\nexport async function getClient(options: ManageClientOptions): Promise<ClientInfo> {\n const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;\n const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${registrationAccessToken}`,\n },\n });\n const body = await readJson<ClientMetadataResponse>(response, \"Reading the client\");\n return toClientInfo(body, clientId);\n}\n\nexport interface UpdateRedirectUrisOptions extends ManageClientOptions {\n redirectUris: string[];\n /**\n * `\"add\"` (default) merges the given URIs into the client's current set;\n * `\"replace\"` makes the given list the exact new set (also how URIs are\n * removed).\n */\n mode?: \"add\" | \"replace\";\n}\n\n/**\n * Updates the allowed OAuth callback URLs of a registered client. Incorta's\n * management endpoint applies `redirect_uris` from the PUT body (other\n * metadata is unchanged server-side).\n */\nexport async function updateClientRedirectUris(\n options: UpdateRedirectUrisOptions,\n): Promise<ClientInfo> {\n const {\n incortaUrl,\n tenant,\n clientId,\n registrationAccessToken,\n redirectUris,\n mode = \"add\",\n fetchImpl = fetch,\n } = options;\n\n if (!redirectUris.length) {\n // An empty replace would leave the client unable to complete the\n // authorization-code flow at all — delete the registration instead.\n throw new Error(\"[incorta-auth] updateClientRedirectUris requires at least one redirect URI.\");\n }\n\n const current = await getClient(options);\n const nextUris =\n mode === \"add\" ? [...new Set([...current.redirectUris, ...redirectUris])] : redirectUris;\n\n const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {\n method: \"PUT\",\n headers: {\n \"content-type\": \"application/json\",\n accept: \"application/json\",\n authorization: `Bearer ${registrationAccessToken}`,\n },\n // RFC 7592 update: client_id + full metadata. Incorta applies\n // redirect_uris only, but nimbus-side parsing wants the full shape.\n body: JSON.stringify({\n client_id: clientId,\n redirect_uris: nextUris,\n grant_types: current.grantTypes,\n scope: current.scope,\n client_name: current.name,\n }),\n });\n\n if (!response.ok) {\n await readJson<ManagementErrorBody>(response, \"Updating the client\"); // throws with details\n }\n // The PUT response body is empty on success — re-read for the fresh state.\n return getClient(options);\n}\n\n/**\n * Deletes a registered client (RFC 7592-style management,\n * `DELETE {incortaUrl}/oauth/{tenant}/register/{clientId}` with the\n * registration access token). Existing sessions in apps using the client keep\n * working until they expire; new logins and token refreshes stop immediately.\n */\nexport async function deleteClient(options: ManageClientOptions): Promise<void> {\n const { incortaUrl, tenant, clientId, registrationAccessToken, fetchImpl = fetch } = options;\n const response = await fetchImpl(registrationEndpoint(incortaUrl, tenant, clientId), {\n method: \"DELETE\",\n headers: {\n accept: \"application/json\",\n authorization: `Bearer ${registrationAccessToken}`,\n },\n });\n if (!response.ok) {\n await readJson<ManagementErrorBody>(response, \"Deleting the client\"); // throws with details\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 {\n deleteClient,\n getClient,\n registerClient,\n updateClientRedirectUris,\n} from \"./core/register.js\";\nexport type {\n ClientInfo,\n ManageClientOptions,\n RegisterClientOptions,\n RegisteredClient,\n UpdateRedirectUrisOptions,\n} 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.d.cts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { IncortaAuth, AuthSession } from './index.cjs';
2
2
  import { IncomingMessage, ServerResponse } from 'node:http';
3
- import './config-B-2cbZDV.cjs';
3
+ import './config-DAKbk8N5.cjs';
4
4
  import 'jose';
5
5
 
6
6
  /**
package/dist/node.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { IncortaAuth, AuthSession } from './index.js';
2
2
  import { IncomingMessage, ServerResponse } from 'node:http';
3
- import './config-B-2cbZDV.js';
3
+ import './config-DAKbk8N5.js';
4
4
  import 'jose';
5
5
 
6
6
  /**
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { a as IncortaUser } from '../config-B-2cbZDV.cjs';
3
+ import { a as IncortaUser } from '../config-DAKbk8N5.cjs';
4
4
  import 'jose';
5
5
 
6
6
  interface SignInOptions {
@@ -1,6 +1,6 @@
1
1
  import * as react from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { a as IncortaUser } from '../config-B-2cbZDV.js';
3
+ import { a as IncortaUser } from '../config-DAKbk8N5.js';
4
4
  import 'jose';
5
5
 
6
6
  interface SignInOptions {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@incorta/auth",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Authenticate Incorta users in any Node.js app — OAuth 2.0/OIDC against Incorta's built-in authorization server, with a React provider and Express/TanStack Start integrations.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,7 +33,8 @@
33
33
  "types": "./dist/node.d.ts",
34
34
  "import": "./dist/node.js",
35
35
  "require": "./dist/node.cjs"
36
- }
36
+ },
37
+ "./package.json": "./package.json"
37
38
  },
38
39
  "bin": {
39
40
  "incorta-auth": "dist/cli.js"