@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 +11 -3
- package/dist/cli.js +30 -2
- package/dist/{config-B-2cbZDV.d.cts → config-DAKbk8N5.d.cts} +12 -0
- package/dist/{config-B-2cbZDV.d.ts → config-DAKbk8N5.d.ts} +12 -0
- package/dist/express.d.cts +1 -1
- package/dist/express.d.ts +1 -1
- package/dist/index.cjs +29 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +28 -6
- package/dist/index.js.map +1 -1
- package/dist/node.d.cts +1 -1
- package/dist/node.d.ts +1 -1
- package/dist/react/index.d.cts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/package.json +3 -2
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;
|
package/dist/express.d.cts
CHANGED
package/dist/express.d.ts
CHANGED
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
|
|
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(
|
|
154
|
+
endpointsCache.delete(cacheKey);
|
|
146
155
|
throw error;
|
|
147
156
|
});
|
|
148
|
-
endpointsCache.set(
|
|
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
|
package/dist/index.cjs.map
CHANGED
|
@@ -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-
|
|
2
|
-
export { a as IncortaUser, b as SessionData } from './config-
|
|
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-
|
|
2
|
-
export { a as IncortaUser, b as SessionData } from './config-
|
|
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
|
|
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(
|
|
123
|
+
endpointsCache.delete(cacheKey);
|
|
116
124
|
throw error;
|
|
117
125
|
});
|
|
118
|
-
endpointsCache.set(
|
|
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
package/dist/node.d.ts
CHANGED
package/dist/react/index.d.cts
CHANGED
package/dist/react/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@incorta/auth",
|
|
3
|
-
"version": "1.
|
|
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"
|