@baseworks/auth 0.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.
@@ -0,0 +1,69 @@
1
+ // src/url-helpers.ts
2
+ function normalizeUrlLike(value) {
3
+ if (value === "/") return "";
4
+ return value.replace(/\/+$/, "");
5
+ }
6
+ function joinPublicUrl(base, path = "") {
7
+ const normalizedBase = normalizeUrlLike(base);
8
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
9
+ if (!normalizedBase) return normalizedPath;
10
+ if (/^https?:\/\//.test(normalizedBase)) {
11
+ return new URL(normalizedPath, `${normalizedBase}/`).toString();
12
+ }
13
+ return `${normalizedBase}${normalizedPath}`;
14
+ }
15
+ function getAuthPublicUrl() {
16
+ return normalizeUrlLike(process.env.NEXT_PUBLIC_AUTH_URL ?? "/auth");
17
+ }
18
+ function getAccountPublicUrl() {
19
+ return normalizeUrlLike(process.env.NEXT_PUBLIC_ACCOUNT_URL ?? "/account");
20
+ }
21
+ function getAppBasePath(defaultPath = "") {
22
+ return normalizeUrlLike(
23
+ process.env.NEXT_PUBLIC_APP_BASE_PATH ?? process.env.APP_BASE_PATH ?? defaultPath
24
+ );
25
+ }
26
+ function getPublicSiteUrl() {
27
+ return normalizeUrlLike(process.env.APP_PUBLIC_URL ?? process.env.NEXT_PUBLIC_SITE_URL ?? "");
28
+ }
29
+ function buildPublicUrl(pathOrUrl, fallbackOrigin) {
30
+ if (/^https?:\/\//.test(pathOrUrl)) return pathOrUrl;
31
+ const origin = getPublicSiteUrl() || fallbackOrigin;
32
+ if (!origin) return pathOrUrl;
33
+ return new URL(pathOrUrl, `${normalizeUrlLike(origin)}/`).toString();
34
+ }
35
+ function buildAuthUrl(path, redirectTo, origin) {
36
+ const target = joinPublicUrl(getAuthPublicUrl(), path);
37
+ const targetIsAbsolute = /^https?:\/\//.test(target);
38
+ const fallbackOrigin = "http://localhost";
39
+ const url = new URL(target, origin ?? fallbackOrigin);
40
+ url.searchParams.set("redirectTo", redirectTo);
41
+ const href = url.toString();
42
+ if (origin || targetIsAbsolute) return href;
43
+ return href.replace(fallbackOrigin, "");
44
+ }
45
+ function buildAuthLoginUrl(redirectTo, origin) {
46
+ return buildAuthUrl("/login", redirectTo, origin);
47
+ }
48
+ function buildAuthLogoutUrl(redirectTo, origin) {
49
+ return buildAuthUrl(
50
+ "/logout",
51
+ process.env.NEXT_PUBLIC_SIGN_OUT_REDIRECT_URL ?? redirectTo,
52
+ origin
53
+ );
54
+ }
55
+ function buildAccountProfileUrl() {
56
+ return joinPublicUrl(getAccountPublicUrl(), "/profile");
57
+ }
58
+
59
+ export {
60
+ normalizeUrlLike,
61
+ getAuthPublicUrl,
62
+ getAccountPublicUrl,
63
+ getAppBasePath,
64
+ getPublicSiteUrl,
65
+ buildPublicUrl,
66
+ buildAuthLoginUrl,
67
+ buildAuthLogoutUrl,
68
+ buildAccountProfileUrl
69
+ };
@@ -0,0 +1,25 @@
1
+ import {
2
+ verifyOidcToken
3
+ } from "./chunk-BL74TFCV.js";
4
+
5
+ // src/oidc-human.ts
6
+ import { decodeJwt } from "jose";
7
+ function createOidcHumanResolver(config) {
8
+ const issuer = config.issuer.replace(/\/+$/, "");
9
+ return async (token) => {
10
+ let iss;
11
+ try {
12
+ iss = decodeJwt(token).iss;
13
+ } catch {
14
+ return null;
15
+ }
16
+ if (!iss || !iss.startsWith(issuer)) return null;
17
+ const identity = await verifyOidcToken(token, { issuer, audience: config.audience });
18
+ if (!identity) return null;
19
+ return config.findOrCreate(identity, token);
20
+ };
21
+ }
22
+
23
+ export {
24
+ createOidcHumanResolver
25
+ };
@@ -0,0 +1,23 @@
1
+ // src/oidc.ts
2
+ import { createRemoteJWKSet, jwtVerify } from "jose";
3
+ function withoutTrailingSlash(v) {
4
+ return v.replace(/\/+$/, "");
5
+ }
6
+ async function verifyOidcToken(token, config) {
7
+ const issuer = withoutTrailingSlash(config.issuer);
8
+ const jwks = createRemoteJWKSet(new URL(`${issuer}/oauth/v2/keys`));
9
+ const opts = config.audience ? { issuer, audience: config.audience } : { issuer };
10
+ const result = await jwtVerify(token, jwks, opts).catch(() => null);
11
+ if (!result) return null;
12
+ const payload = result.payload;
13
+ const subject = String(payload.sub ?? "");
14
+ if (!subject) return null;
15
+ const email = String(payload.email ?? payload.preferred_username ?? `${subject}@unknown`);
16
+ const name = String(payload.name ?? payload.preferred_username ?? email);
17
+ const picture = payload.picture ? String(payload.picture) : void 0;
18
+ return { subject, issuer, email, name, picture };
19
+ }
20
+
21
+ export {
22
+ verifyOidcToken
23
+ };
@@ -0,0 +1,23 @@
1
+ // src/zitadel.ts
2
+ import { createRemoteJWKSet, jwtVerify } from "jose";
3
+ function withoutTrailingSlash(v) {
4
+ return v.replace(/\/+$/, "");
5
+ }
6
+ async function verifyZitadelToken(token, config) {
7
+ const issuer = withoutTrailingSlash(config.issuer);
8
+ const jwks = createRemoteJWKSet(new URL(`${issuer}/oauth/v2/keys`));
9
+ const opts = config.audience ? { issuer, audience: config.audience } : { issuer };
10
+ const result = await jwtVerify(token, jwks, opts).catch(() => null);
11
+ if (!result) return null;
12
+ const payload = result.payload;
13
+ const subject = String(payload.sub ?? "");
14
+ if (!subject) return null;
15
+ const email = String(payload.email ?? payload.preferred_username ?? `${subject}@zitadel.local`);
16
+ const name = String(payload.name ?? payload.preferred_username ?? email);
17
+ const picture = payload.picture ? String(payload.picture) : void 0;
18
+ return { subject, issuer, email, name, picture };
19
+ }
20
+
21
+ export {
22
+ verifyZitadelToken
23
+ };
@@ -0,0 +1,47 @@
1
+ // src/cli-auth.ts
2
+ async function startCliAuth(apiBase) {
3
+ const url = `${apiBase.replace(/\/+$/, "")}/v1/auth/start`;
4
+ const res = await fetch(url);
5
+ if (!res.ok) {
6
+ const text = await res.text().catch(() => res.statusText);
7
+ throw new Error(`Auth start failed (${res.status}): ${text}`);
8
+ }
9
+ const data = await res.json();
10
+ if (!data.state || !data.url) {
11
+ throw new Error("Invalid response from auth/start");
12
+ }
13
+ return {
14
+ state: data.state,
15
+ url: data.url,
16
+ expiresIn: data.expires_in ?? 600
17
+ };
18
+ }
19
+ async function pollCliAuth(apiBase, state, opts = {}) {
20
+ const intervalMs = opts.intervalMs ?? 2e3;
21
+ const timeoutMs = opts.timeoutMs ?? 3e5;
22
+ const base = apiBase.replace(/\/+$/, "");
23
+ const deadline = Date.now() + timeoutMs;
24
+ while (Date.now() < deadline) {
25
+ await sleep(intervalMs);
26
+ const res = await fetch(`${base}/v1/auth/poll/${state}`);
27
+ if (!res.ok) {
28
+ throw new Error(`Poll failed (${res.status})`);
29
+ }
30
+ const data = await res.json();
31
+ if (data.status === "done" && data.token) {
32
+ return { token: data.token };
33
+ }
34
+ if (data.status === "expired") {
35
+ throw new Error("Auth session expired \u2014 run login again");
36
+ }
37
+ }
38
+ throw new Error("Auth timed out \u2014 run login again");
39
+ }
40
+ function sleep(ms) {
41
+ return new Promise((resolve) => setTimeout(resolve, ms));
42
+ }
43
+
44
+ export {
45
+ startCliAuth,
46
+ pollCliAuth
47
+ };
@@ -0,0 +1,33 @@
1
+ // src/pkce.ts
2
+ async function generatePkce() {
3
+ const array = new Uint8Array(32);
4
+ crypto.getRandomValues(array);
5
+ const verifier = base64url(array);
6
+ const encoded = new TextEncoder().encode(verifier);
7
+ const digest = await crypto.subtle.digest("SHA-256", encoded);
8
+ const challenge = base64url(new Uint8Array(digest));
9
+ return { verifier, challenge, method: "S256" };
10
+ }
11
+ function buildOidcAuthUrl(config) {
12
+ const issuer = config.issuer.replace(/\/+$/, "");
13
+ const scopes = (config.scopes ?? ["openid", "email", "profile"]).join(" ");
14
+ const params = new URLSearchParams({
15
+ client_id: config.clientId,
16
+ redirect_uri: config.redirectUri,
17
+ response_type: "code",
18
+ scope: scopes,
19
+ code_challenge: config.challenge,
20
+ code_challenge_method: "S256"
21
+ });
22
+ if (config.state) params.set("state", config.state);
23
+ if (config.prompt) params.set("prompt", config.prompt);
24
+ return `${issuer}/oauth/v2/authorize?${params.toString()}`;
25
+ }
26
+ function base64url(buf) {
27
+ return btoa(Array.from(buf, (b) => String.fromCharCode(b)).join("")).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
28
+ }
29
+
30
+ export {
31
+ generatePkce,
32
+ buildOidcAuthUrl
33
+ };
@@ -0,0 +1,20 @@
1
+ // src/token.ts
2
+ async function hashToken(token, pepper) {
3
+ const input = pepper ? `${pepper}:${token}` : token;
4
+ const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
5
+ return Array.from(new Uint8Array(buf), (b) => b.toString(16).padStart(2, "0")).join("");
6
+ }
7
+ function looksLikeJwt(token) {
8
+ return token.split(".").length === 3;
9
+ }
10
+ function stripBearer(header) {
11
+ if (!header?.startsWith("Bearer ")) return null;
12
+ const token = header.slice(7).trim();
13
+ return token || null;
14
+ }
15
+
16
+ export {
17
+ hashToken,
18
+ looksLikeJwt,
19
+ stripBearer
20
+ };
@@ -0,0 +1,402 @@
1
+ import {
2
+ getAuthPublicUrl,
3
+ normalizeUrlLike
4
+ } from "./chunk-3NJASEF4.js";
5
+
6
+ // src/session.ts
7
+ import { cookies } from "next/headers";
8
+ import { NextResponse } from "next/server";
9
+ var oidcCookies = {
10
+ codeVerifier: "oidc_code_verifier",
11
+ state: "oidc_state",
12
+ nonce: "oidc_nonce",
13
+ returnTo: "oidc_return_to",
14
+ idToken: "oidc_id_token",
15
+ accessToken: "oidc_access_token",
16
+ refreshToken: "oidc_refresh_token",
17
+ expiresAt: "oidc_expires_at",
18
+ session: "oidc_session"
19
+ };
20
+ function getIssuer() {
21
+ return (process.env.OIDC_ISSUER ?? "https://nesskey.com").replace(/\/+$/, "");
22
+ }
23
+ function getClientId() {
24
+ const clientId = process.env.OIDC_CLIENT_ID;
25
+ if (!clientId) throw new Error("OIDC_CLIENT_ID is required.");
26
+ return clientId;
27
+ }
28
+ function getOptionalClientSecret() {
29
+ return process.env.OIDC_CLIENT_SECRET;
30
+ }
31
+ function getCookieDomain() {
32
+ const domain = process.env.OIDC_COOKIE_DOMAIN?.trim();
33
+ return domain || void 0;
34
+ }
35
+ function getScope() {
36
+ return process.env.OIDC_SCOPE ?? "openid profile email offline_access";
37
+ }
38
+ function getPublicAuthBasePath() {
39
+ return normalizeUrlLike(process.env.OIDC_PUBLIC_BASE_PATH ?? getAuthPublicUrl() ?? "/auth");
40
+ }
41
+ function getRedirectUriOverride() {
42
+ return process.env.OIDC_REDIRECT_URI ?? null;
43
+ }
44
+ function getDefaultSignedOutRedirect() {
45
+ return process.env.OIDC_DEFAULT_REDIRECT ?? "/";
46
+ }
47
+ function getPublicSiteUrlFromEnv(request) {
48
+ const explicit = process.env.APP_PUBLIC_URL ?? process.env.NEXT_PUBLIC_SITE_URL ?? process.env.OIDC_PUBLIC_SITE_URL;
49
+ if (explicit) return normalizeUrlLike(explicit);
50
+ if (request) return normalizeUrlLike(request.nextUrl.origin);
51
+ return null;
52
+ }
53
+ function resolvePublicRedirect(target, request) {
54
+ if (/^https?:\/\//.test(target)) return new URL(target);
55
+ const publicSiteUrl = getPublicSiteUrlFromEnv(request);
56
+ if (publicSiteUrl) return new URL(target, publicSiteUrl);
57
+ return new URL(target, request.url);
58
+ }
59
+ function base64UrlEncode(input) {
60
+ const buffer = typeof input === "string" ? Buffer.from(input, "utf8") : Buffer.from(input instanceof Uint8Array ? input : new Uint8Array(input));
61
+ return buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
62
+ }
63
+ function base64UrlDecode(input) {
64
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
65
+ const remainder = padded.length % 4;
66
+ const normalized = remainder === 0 ? padded : `${padded}${"=".repeat(4 - remainder)}`;
67
+ return Buffer.from(normalized, "base64").toString("utf8");
68
+ }
69
+ function sha256(input) {
70
+ return crypto.subtle.digest("SHA-256", new TextEncoder().encode(input));
71
+ }
72
+ function randomString() {
73
+ return base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)));
74
+ }
75
+ async function createPkcePair() {
76
+ const verifier = randomString();
77
+ const challenge = base64UrlEncode(await sha256(verifier));
78
+ return { challenge, verifier };
79
+ }
80
+ async function discoverOidc() {
81
+ const issuer = getIssuer();
82
+ const response = await fetch(`${issuer}/.well-known/openid-configuration`);
83
+ if (!response.ok) throw new Error(`Failed to load OIDC discovery document from ${issuer}.`);
84
+ return await response.json();
85
+ }
86
+ function parseJwtPayload(token) {
87
+ const [, payload = ""] = token.split(".");
88
+ return JSON.parse(base64UrlDecode(payload));
89
+ }
90
+ function encodeSessionCookie(session) {
91
+ return base64UrlEncode(JSON.stringify(session));
92
+ }
93
+ function parseSessionCookie(value) {
94
+ try {
95
+ const session = JSON.parse(base64UrlDecode(value));
96
+ return session.isAuthenticated ? session : null;
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+ function encodeTransactionCookie(transaction) {
102
+ return base64UrlEncode(JSON.stringify(transaction));
103
+ }
104
+ function parseTransactionCookie(value) {
105
+ try {
106
+ const transaction = JSON.parse(base64UrlDecode(value));
107
+ if (!transaction.codeVerifier || !transaction.returnTo) return null;
108
+ return transaction;
109
+ } catch {
110
+ return null;
111
+ }
112
+ }
113
+ function getTransactionCookieName(state) {
114
+ return `oidc_tx_${state}`;
115
+ }
116
+ function getCookieOptions(request) {
117
+ return {
118
+ domain: getCookieDomain(),
119
+ httpOnly: true,
120
+ path: "/",
121
+ sameSite: "lax",
122
+ secure: request.nextUrl.protocol === "https:"
123
+ };
124
+ }
125
+ function expireCookie(response, request, name) {
126
+ response.cookies.set(name, "", { ...getCookieOptions(request), expires: /* @__PURE__ */ new Date(0) });
127
+ }
128
+ function setTransactionCookie(response, request, state, transaction) {
129
+ response.cookies.set(
130
+ getTransactionCookieName(state),
131
+ encodeTransactionCookie(transaction),
132
+ { ...getCookieOptions(request), expires: new Date(Date.now() + 10 * 60 * 1e3) }
133
+ );
134
+ }
135
+ function getRedirectUri(request) {
136
+ const override = getRedirectUriOverride();
137
+ if (override) return override;
138
+ return new URL(
139
+ `${getPublicAuthBasePath()}/oidc/callback`,
140
+ getPublicSiteUrlFromEnv(request) ?? request.nextUrl.origin
141
+ ).toString();
142
+ }
143
+ function appendClientAuthentication(headers, params, clientId, clientSecret) {
144
+ if (clientSecret) {
145
+ const basic = Buffer.from(`${clientId}:${clientSecret}`).toString("base64");
146
+ headers.set("authorization", `Basic ${basic}`);
147
+ return;
148
+ }
149
+ params.set("client_id", clientId);
150
+ }
151
+ async function exchangeAuthorizationCode(args) {
152
+ const params = new URLSearchParams({
153
+ code: args.code,
154
+ code_verifier: args.codeVerifier,
155
+ grant_type: "authorization_code",
156
+ redirect_uri: args.redirectUri
157
+ });
158
+ const reqHeaders = new Headers({ "content-type": "application/x-www-form-urlencoded" });
159
+ appendClientAuthentication(reqHeaders, params, args.clientId, args.clientSecret);
160
+ const response = await fetch(args.tokenEndpoint, {
161
+ body: params.toString(),
162
+ headers: reqHeaders,
163
+ method: "POST"
164
+ });
165
+ const json = await response.json();
166
+ if (!response.ok || !json.id_token) {
167
+ throw new Error(json.error_description ?? "OIDC code exchange failed.");
168
+ }
169
+ return json;
170
+ }
171
+ async function refreshIdToken(args) {
172
+ const params = new URLSearchParams({
173
+ grant_type: "refresh_token",
174
+ refresh_token: args.refreshToken
175
+ });
176
+ const reqHeaders = new Headers({ "content-type": "application/x-www-form-urlencoded" });
177
+ appendClientAuthentication(reqHeaders, params, args.clientId, args.clientSecret);
178
+ const response = await fetch(args.tokenEndpoint, {
179
+ body: params.toString(),
180
+ headers: reqHeaders,
181
+ method: "POST"
182
+ });
183
+ const json = await response.json();
184
+ if (!response.ok || !json.id_token) {
185
+ throw new Error(json.error_description ?? "OIDC token refresh failed.");
186
+ }
187
+ return json;
188
+ }
189
+ function sessionFromPayload(payload) {
190
+ return {
191
+ audience: payload.aud,
192
+ email: payload.email,
193
+ expiresAt: payload.exp ?? 0,
194
+ isAuthenticated: true,
195
+ issuer: payload.iss,
196
+ name: payload.name,
197
+ pictureUrl: payload.picture,
198
+ subject: payload.sub,
199
+ tokenIdentifier: payload.sub && payload.iss ? `${payload.sub}|${payload.iss}` : void 0
200
+ };
201
+ }
202
+ function applyTokenCookies(response, request, tokens) {
203
+ const options = getCookieOptions(request);
204
+ const payload = parseJwtPayload(tokens.id_token);
205
+ const expiresAt = payload.exp ?? (tokens.expires_in ? Math.floor(Date.now() / 1e3) + tokens.expires_in : void 0);
206
+ if (!expiresAt) throw new Error("The OIDC ID token did not include an expiry.");
207
+ response.cookies.set(oidcCookies.idToken, tokens.id_token, {
208
+ ...options,
209
+ expires: new Date(expiresAt * 1e3)
210
+ });
211
+ response.cookies.set(oidcCookies.expiresAt, String(expiresAt), {
212
+ ...options,
213
+ expires: new Date(expiresAt * 1e3)
214
+ });
215
+ response.cookies.set(
216
+ oidcCookies.session,
217
+ encodeSessionCookie(sessionFromPayload({ ...payload, exp: expiresAt })),
218
+ { ...options, expires: new Date(expiresAt * 1e3) }
219
+ );
220
+ if (tokens.access_token) {
221
+ response.cookies.set(oidcCookies.accessToken, tokens.access_token, {
222
+ ...options,
223
+ expires: new Date(expiresAt * 1e3)
224
+ });
225
+ }
226
+ if (tokens.refresh_token) {
227
+ response.cookies.set(oidcCookies.refreshToken, tokens.refresh_token, options);
228
+ }
229
+ }
230
+ function clearTransientCookies(response, request) {
231
+ expireCookie(response, request, oidcCookies.codeVerifier);
232
+ expireCookie(response, request, oidcCookies.state);
233
+ expireCookie(response, request, oidcCookies.nonce);
234
+ }
235
+ async function buildAuthorizationRedirect(request) {
236
+ const discovery = await discoverOidc();
237
+ const clientId = getClientId();
238
+ const { challenge, verifier } = await createPkcePair();
239
+ const nonce = randomString();
240
+ const state = randomString();
241
+ const redirectUri = getRedirectUri(request);
242
+ const returnTo = request.nextUrl.searchParams.get("redirectTo") ?? getDefaultSignedOutRedirect();
243
+ const url = new URL(discovery.authorization_endpoint);
244
+ url.searchParams.set("client_id", clientId);
245
+ url.searchParams.set("code_challenge", challenge);
246
+ url.searchParams.set("code_challenge_method", "S256");
247
+ url.searchParams.set("nonce", nonce);
248
+ url.searchParams.set("redirect_uri", redirectUri);
249
+ url.searchParams.set("response_type", "code");
250
+ url.searchParams.set("scope", getScope());
251
+ url.searchParams.set("state", state);
252
+ const response = NextResponse.redirect(url);
253
+ const options = getCookieOptions(request);
254
+ setTransactionCookie(response, request, state, { codeVerifier: verifier, nonce, returnTo });
255
+ response.cookies.set(oidcCookies.codeVerifier, verifier, options);
256
+ response.cookies.set(oidcCookies.state, state, options);
257
+ response.cookies.set(oidcCookies.nonce, nonce, options);
258
+ response.cookies.set(oidcCookies.returnTo, returnTo, options);
259
+ return response;
260
+ }
261
+ async function handleAuthorizationCallback(request) {
262
+ const cookieStore = await cookies();
263
+ const code = request.nextUrl.searchParams.get("code");
264
+ const state = request.nextUrl.searchParams.get("state");
265
+ const transaction = state ? parseTransactionCookie(cookieStore.get(getTransactionCookieName(state))?.value ?? "") : null;
266
+ const savedState = cookieStore.get(oidcCookies.state)?.value;
267
+ const codeVerifier = transaction?.codeVerifier ?? cookieStore.get(oidcCookies.codeVerifier)?.value;
268
+ const savedNonce = transaction?.nonce ?? cookieStore.get(oidcCookies.nonce)?.value;
269
+ const returnTo = transaction?.returnTo ?? cookieStore.get(oidcCookies.returnTo)?.value ?? getDefaultSignedOutRedirect();
270
+ if (!code || !state || !codeVerifier || !transaction && (!savedState || state !== savedState)) {
271
+ return NextResponse.redirect(
272
+ resolvePublicRedirect(`${getPublicAuthBasePath()}?error=oidc_state`, request)
273
+ );
274
+ }
275
+ try {
276
+ const discovery = await discoverOidc();
277
+ const tokens = await exchangeAuthorizationCode({
278
+ clientId: getClientId(),
279
+ clientSecret: getOptionalClientSecret(),
280
+ code,
281
+ codeVerifier,
282
+ redirectUri: getRedirectUri(request),
283
+ tokenEndpoint: discovery.token_endpoint
284
+ });
285
+ const payload = parseJwtPayload(tokens.id_token);
286
+ if (savedNonce && payload.nonce && payload.nonce !== savedNonce) {
287
+ throw new Error("OIDC nonce mismatch.");
288
+ }
289
+ const response = NextResponse.redirect(resolvePublicRedirect(returnTo, request));
290
+ applyTokenCookies(response, request, tokens);
291
+ clearTransientCookies(response, request);
292
+ expireCookie(response, request, oidcCookies.returnTo);
293
+ expireCookie(response, request, getTransactionCookieName(state));
294
+ return response;
295
+ } catch {
296
+ return NextResponse.redirect(
297
+ resolvePublicRedirect(`${getPublicAuthBasePath()}?error=oidc_callback`, request)
298
+ );
299
+ }
300
+ }
301
+ async function getSessionFromCookies() {
302
+ const cookieStore = await cookies();
303
+ const idToken = cookieStore.get(oidcCookies.idToken)?.value;
304
+ if (idToken) {
305
+ try {
306
+ return sessionFromPayload(parseJwtPayload(idToken));
307
+ } catch {
308
+ }
309
+ }
310
+ const sessionCookie = cookieStore.get(oidcCookies.session)?.value;
311
+ const session = sessionCookie ? parseSessionCookie(sessionCookie) : null;
312
+ return session ?? { expiresAt: 0, isAuthenticated: false };
313
+ }
314
+ async function getServerIdToken() {
315
+ const cookieStore = await cookies();
316
+ return cookieStore.get(oidcCookies.idToken)?.value ?? null;
317
+ }
318
+ async function hasServerOidcSession() {
319
+ const cookieStore = await cookies();
320
+ return Boolean(
321
+ cookieStore.get(oidcCookies.session)?.value ?? cookieStore.get(oidcCookies.idToken)?.value ?? cookieStore.get(oidcCookies.accessToken)?.value
322
+ );
323
+ }
324
+ async function getServerAccessToken() {
325
+ const cookieStore = await cookies();
326
+ const accessToken = cookieStore.get(oidcCookies.accessToken)?.value;
327
+ if (accessToken) return accessToken;
328
+ const refreshToken = cookieStore.get(oidcCookies.refreshToken)?.value;
329
+ if (!refreshToken) return null;
330
+ try {
331
+ const discovery = await discoverOidc();
332
+ const tokens = await refreshIdToken({
333
+ clientId: getClientId(),
334
+ clientSecret: getOptionalClientSecret(),
335
+ refreshToken,
336
+ tokenEndpoint: discovery.token_endpoint
337
+ });
338
+ return tokens.access_token ?? null;
339
+ } catch {
340
+ return null;
341
+ }
342
+ }
343
+ async function buildSessionResponse(_request) {
344
+ const cookieStore = await cookies();
345
+ const idToken = cookieStore.get(oidcCookies.idToken)?.value;
346
+ const expiresAt = Number(cookieStore.get(oidcCookies.expiresAt)?.value ?? "0");
347
+ const now = Math.floor(Date.now() / 1e3);
348
+ if (!idToken || expiresAt <= now) {
349
+ return NextResponse.json({ expiresAt: 0, isAuthenticated: false }, {
350
+ status: 401
351
+ });
352
+ }
353
+ return NextResponse.json(sessionFromPayload({ ...parseJwtPayload(idToken), exp: expiresAt }));
354
+ }
355
+ async function buildTokenResponse(request) {
356
+ const cookieStore = await cookies();
357
+ const idToken = cookieStore.get(oidcCookies.idToken)?.value;
358
+ const expiresAt = Number(cookieStore.get(oidcCookies.expiresAt)?.value ?? "0");
359
+ const now = Math.floor(Date.now() / 1e3);
360
+ if (!idToken || expiresAt <= now) {
361
+ const response = NextResponse.json({ token: null }, { status: 401 });
362
+ expireCookie(response, request, oidcCookies.idToken);
363
+ expireCookie(response, request, oidcCookies.refreshToken);
364
+ expireCookie(response, request, oidcCookies.expiresAt);
365
+ return response;
366
+ }
367
+ return NextResponse.json({ token: idToken });
368
+ }
369
+ async function buildLogoutResponse(request) {
370
+ const redirectTo = request.nextUrl.searchParams.get("redirectTo") ?? getDefaultSignedOutRedirect();
371
+ const postLogoutUrl = resolvePublicRedirect(redirectTo, request).toString();
372
+ const cookieStore = await cookies();
373
+ const idToken = cookieStore.get(oidcCookies.idToken)?.value;
374
+ const clearResponse = NextResponse.redirect(postLogoutUrl);
375
+ Object.values(oidcCookies).forEach((name) => expireCookie(clearResponse, request, name));
376
+ try {
377
+ const discovery = await discoverOidc();
378
+ if (discovery.end_session_endpoint) {
379
+ const endSessionUrl = new URL(discovery.end_session_endpoint);
380
+ endSessionUrl.searchParams.set("post_logout_redirect_uri", postLogoutUrl);
381
+ if (idToken) endSessionUrl.searchParams.set("id_token_hint", idToken);
382
+ return NextResponse.redirect(endSessionUrl.toString(), {
383
+ headers: clearResponse.headers
384
+ });
385
+ }
386
+ } catch {
387
+ }
388
+ return clearResponse;
389
+ }
390
+
391
+ export {
392
+ oidcCookies,
393
+ buildAuthorizationRedirect,
394
+ handleAuthorizationCallback,
395
+ getSessionFromCookies,
396
+ getServerIdToken,
397
+ hasServerOidcSession,
398
+ getServerAccessToken,
399
+ buildSessionResponse,
400
+ buildTokenResponse,
401
+ buildLogoutResponse
402
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * CLI authentication helpers — PKCE polling flow.
3
+ * Used by any CLI tool that authenticates via an orbseal-compatible API.
4
+ *
5
+ * Flow:
6
+ * 1. startCliAuth() → get state + URL to show user
7
+ * 2. pollCliAuth() → long-poll until user completes browser auth
8
+ */
9
+ interface CliAuthStart {
10
+ state: string;
11
+ url: string;
12
+ expiresIn: number;
13
+ }
14
+ interface CliAuthResult {
15
+ token: string;
16
+ }
17
+ interface PollOptions {
18
+ /** ms between polls (default: 2000) */
19
+ intervalMs?: number;
20
+ /** ms before giving up (default: 300_000 = 5 min) */
21
+ timeoutMs?: number;
22
+ }
23
+ /**
24
+ * Call the API to initiate CLI login.
25
+ * Returns the state handle and the URL the user should open.
26
+ */
27
+ declare function startCliAuth(apiBase: string): Promise<CliAuthStart>;
28
+ /**
29
+ * Poll the API until the user completes browser auth.
30
+ * Resolves with the token when done, rejects on timeout or error.
31
+ */
32
+ declare function pollCliAuth(apiBase: string, state: string, opts?: PollOptions): Promise<CliAuthResult>;
33
+
34
+ export { type CliAuthResult, type CliAuthStart, type PollOptions, pollCliAuth, startCliAuth };
@@ -0,0 +1,8 @@
1
+ import {
2
+ pollCliAuth,
3
+ startCliAuth
4
+ } from "./chunk-C4V5LCFA.js";
5
+ export {
6
+ pollCliAuth,
7
+ startCliAuth
8
+ };