@geekapps/auth-nextjs 0.1.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/dist/server.js ADDED
@@ -0,0 +1,701 @@
1
+ // src/server.ts
2
+ import { cookies, headers } from "next/headers";
3
+ import { redirect } from "next/navigation";
4
+ import { NextResponse } from "next/server";
5
+ import { createRemoteJWKSet, jwtVerify } from "jose";
6
+ var MAX_ACCOUNTS = 8;
7
+ function normalizeBaseUrl(url) {
8
+ return url.replace(/\/$/, "");
9
+ }
10
+ function normalizeBasePath(path = "/api/auth") {
11
+ if (!path.startsWith("/")) return `/${path}`;
12
+ return path.replace(/\/$/, "");
13
+ }
14
+ function createUrl(baseUrl, path) {
15
+ if (/^https?:\/\//.test(path)) return path;
16
+ const normalizedPath = path.startsWith("/") ? path : `/${path}`;
17
+ return `${normalizeBaseUrl(baseUrl)}${normalizedPath}`;
18
+ }
19
+ function env(name) {
20
+ const value = process.env[name];
21
+ return value && value.trim().length > 0 ? value : void 0;
22
+ }
23
+ function resolveIssuer(input) {
24
+ return normalizeBaseUrl(
25
+ input.issuer ?? env("GEEKAPPS_AUTH_ISSUER") ?? env("GEEKAPPS_AUTH_API_URL") ?? "http://localhost:3020"
26
+ );
27
+ }
28
+ function resolveClientId(input) {
29
+ return input.clientId ?? env("GEEKAPPS_AUTH_CLIENT_ID") ?? "";
30
+ }
31
+ function resolveClientSecret(input) {
32
+ return input.clientSecret ?? env("GEEKAPPS_AUTH_CLIENT_SECRET");
33
+ }
34
+ function applyServerClientCredentials(headers2, config) {
35
+ if (!config.clientId || !config.clientSecret) {
36
+ return;
37
+ }
38
+ headers2.set("x-geekapps-client-id", config.clientId);
39
+ headers2.set("x-geekapps-client-secret", config.clientSecret);
40
+ }
41
+ function resolveConfiguredRedirectUri(input) {
42
+ return input.redirectUri ?? env("GEEKAPPS_AUTH_REDIRECT_URI");
43
+ }
44
+ function hashClientId(clientId) {
45
+ let h = 5381;
46
+ for (let i = 0; i < clientId.length; i++) {
47
+ h = (h << 5) + h ^ clientId.charCodeAt(i) & 255;
48
+ h = h >>> 0;
49
+ }
50
+ return h.toString(16).padStart(8, "0");
51
+ }
52
+ function deriveCookiePrefix(clientId) {
53
+ if (!clientId) return "gk";
54
+ return `gk_${hashClientId(clientId)}`;
55
+ }
56
+ function requestOrigin(request) {
57
+ const forwardedProto = request.headers.get("x-forwarded-proto");
58
+ const forwardedHost = request.headers.get("x-forwarded-host");
59
+ const host = forwardedHost ?? request.headers.get("host");
60
+ if (host) {
61
+ return `${forwardedProto ?? request.nextUrl.protocol.replace(":", "")}://${host}`;
62
+ }
63
+ return request.nextUrl.origin;
64
+ }
65
+ function resolveRedirectUri(config, request) {
66
+ return config.redirectUri ?? `${requestOrigin(request)}${config.basePath}/callback`;
67
+ }
68
+ function tokenFromResponse(data) {
69
+ return {
70
+ accessToken: data.accessToken ?? data.access_token ?? data.tokens?.accessToken ?? data.tokens?.access_token ?? null,
71
+ refreshToken: data.refreshToken ?? data.refresh_token ?? data.tokens?.refreshToken ?? data.tokens?.refresh_token ?? null,
72
+ idToken: data.idToken ?? data.id_token ?? data.tokens?.idToken ?? data.tokens?.id_token ?? null,
73
+ sessionId: data.sessionId ?? data.session_id ?? data.tokens?.sessionId ?? data.tokens?.session_id ?? null
74
+ };
75
+ }
76
+ function decodeJwtPayload(token) {
77
+ try {
78
+ const parts = token.split(".");
79
+ if (parts.length < 2) return null;
80
+ const payload = parts[1];
81
+ if (!payload) return null;
82
+ return JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+ function parseAccountFromJwt(tokens) {
88
+ if (!tokens.accessToken) return null;
89
+ const jwtToParse = tokens.idToken ?? tokens.accessToken;
90
+ const payload = decodeJwtPayload(jwtToParse);
91
+ if (!payload) return null;
92
+ const sub = typeof payload.sub === "string" ? payload.sub : null;
93
+ const id = sub ?? tokens.sessionId;
94
+ if (!id) return null;
95
+ return {
96
+ id,
97
+ accessToken: tokens.accessToken,
98
+ refreshToken: tokens.refreshToken,
99
+ idToken: tokens.idToken ?? null,
100
+ sessionId: tokens.sessionId,
101
+ user: {
102
+ sub: id,
103
+ name: typeof payload.name === "string" ? payload.name : null,
104
+ email: typeof payload.email === "string" ? payload.email : null,
105
+ picture: typeof payload.picture === "string" ? payload.picture : null,
106
+ phone: typeof payload.phone_number === "string" ? payload.phone_number : null
107
+ }
108
+ };
109
+ }
110
+ function getCookieNames(prefix) {
111
+ return {
112
+ active: `${prefix}_active`,
113
+ count: `${prefix}_count`,
114
+ activeOrganizationId: `${prefix}_ao`,
115
+ tokenPub: `${prefix}_tp`,
116
+ slotMeta: (n) => `${prefix}_m${n}`,
117
+ slotAt: (n) => `${prefix}_at${n}`,
118
+ slotRt: (n) => `${prefix}_rt${n}`
119
+ };
120
+ }
121
+ function encodeMeta(account) {
122
+ const m = { id: account.id };
123
+ if (account.sessionId) m.sid = account.sessionId;
124
+ if (account.user.name) m.n = account.user.name;
125
+ if (account.user.email) m.e = account.user.email;
126
+ if (account.user.picture) m.p = account.user.picture;
127
+ return Buffer.from(JSON.stringify(m), "utf8").toString("base64url");
128
+ }
129
+ function decodeMeta(value) {
130
+ try {
131
+ const m = JSON.parse(Buffer.from(value, "base64url").toString("utf8"));
132
+ return m.id ? m : null;
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+ function getCookieOptions(config) {
138
+ return {
139
+ httpOnly: true,
140
+ secure: config.secureCookies ?? process.env.NODE_ENV === "production",
141
+ sameSite: "lax",
142
+ path: "/",
143
+ ...config.cookieDomain ? { domain: config.cookieDomain } : {}
144
+ };
145
+ }
146
+ async function readBody(request) {
147
+ const text = await request.text();
148
+ if (!text) return void 0;
149
+ try {
150
+ return JSON.parse(text);
151
+ } catch {
152
+ return text;
153
+ }
154
+ }
155
+ function appendSearchParams(url, request) {
156
+ const target = new URL(url);
157
+ request.nextUrl.searchParams.forEach((value, key) => {
158
+ target.searchParams.set(key, value);
159
+ });
160
+ return target.toString();
161
+ }
162
+ function routeFromRequest(request, basePath) {
163
+ const pathname = request.nextUrl.pathname;
164
+ if (!pathname.startsWith(basePath)) return pathname;
165
+ const route = pathname.slice(basePath.length);
166
+ return route || "/";
167
+ }
168
+ function isJsonResponse(response) {
169
+ return response.headers.get("content-type")?.includes("application/json");
170
+ }
171
+ async function responseFromApi(response) {
172
+ if (response.status === 204) return new NextResponse(null, { status: 204 });
173
+ const body = isJsonResponse(response) ? await response.json().catch(() => null) : await response.text();
174
+ return NextResponse.json(body, { status: response.status });
175
+ }
176
+ var _remoteJWKSCache = /* @__PURE__ */ new Map();
177
+ function getRemoteJWKS(issuer) {
178
+ if (!_remoteJWKSCache.has(issuer)) {
179
+ _remoteJWKSCache.set(issuer, createRemoteJWKSet(new URL(`${issuer}/.well-known/jwks.json`)));
180
+ }
181
+ return _remoteJWKSCache.get(issuer);
182
+ }
183
+ async function verifyToken(token, issuer) {
184
+ try {
185
+ const { payload } = await jwtVerify(token, getRemoteJWKS(issuer), {
186
+ algorithms: ["RS256"],
187
+ issuer
188
+ });
189
+ return payload;
190
+ } catch {
191
+ return null;
192
+ }
193
+ }
194
+ function configureGeekappsAuth(input = {}) {
195
+ const clientId = resolveClientId(input);
196
+ const config = {
197
+ ...input,
198
+ issuer: resolveIssuer(input),
199
+ clientId,
200
+ clientSecret: resolveClientSecret(input),
201
+ redirectUri: resolveConfiguredRedirectUri(input),
202
+ afterSignInRedirectTo: input.afterSignInRedirectTo ?? input.redirectTo ?? "/",
203
+ basePath: normalizeBasePath(input.basePath),
204
+ cookiePrefix: deriveCookiePrefix(clientId)
205
+ };
206
+ const cn = getCookieNames(config.cookiePrefix);
207
+ async function getCount() {
208
+ const cookieStore = await cookies();
209
+ const raw = cookieStore.get(cn.count)?.value;
210
+ const n = raw ? Number.parseInt(raw, 10) : 0;
211
+ return Number.isNaN(n) ? 0 : n;
212
+ }
213
+ async function getActiveIndex() {
214
+ const cookieStore = await cookies();
215
+ const raw = cookieStore.get(cn.active)?.value;
216
+ if (!raw) return 0;
217
+ const n = Number.parseInt(raw, 10);
218
+ return Number.isNaN(n) ? 0 : n;
219
+ }
220
+ function readSlot(cookieStore, n) {
221
+ const at = cookieStore.get(cn.slotAt(n))?.value;
222
+ if (!at) return null;
223
+ const meta = decodeMeta(cookieStore.get(cn.slotMeta(n))?.value ?? "");
224
+ if (!meta) return null;
225
+ return {
226
+ id: meta.id,
227
+ accessToken: at,
228
+ refreshToken: cookieStore.get(cn.slotRt(n))?.value ?? null,
229
+ idToken: null,
230
+ // not stored in cookies; claims are in meta
231
+ sessionId: meta.sid ?? null,
232
+ user: {
233
+ sub: meta.id,
234
+ name: meta.n ?? null,
235
+ email: meta.e ?? null,
236
+ picture: meta.p ?? null
237
+ }
238
+ };
239
+ }
240
+ function writeSlot(cookieStore, n, account) {
241
+ const opts = { ...getCookieOptions(config), maxAge: 60 * 60 * 24 * 30 };
242
+ cookieStore.set(cn.slotMeta(n), encodeMeta(account), opts);
243
+ cookieStore.set(cn.slotAt(n), account.accessToken, opts);
244
+ if (account.refreshToken) cookieStore.set(cn.slotRt(n), account.refreshToken, opts);
245
+ }
246
+ function clearSlot(cookieStore, n) {
247
+ const clear = { ...getCookieOptions(config), maxAge: 0 };
248
+ cookieStore.set(cn.slotMeta(n), "", clear);
249
+ cookieStore.set(cn.slotAt(n), "", clear);
250
+ cookieStore.set(cn.slotRt(n), "", clear);
251
+ }
252
+ async function getAccountsList() {
253
+ const [cookieStore, count] = await Promise.all([cookies(), getCount()]);
254
+ const accounts = [];
255
+ for (let i = 1; i <= count; i++) {
256
+ const slot = readSlot(cookieStore, i);
257
+ if (slot) accounts.push(slot);
258
+ }
259
+ return accounts;
260
+ }
261
+ async function getActiveAccount() {
262
+ const [cookieStore, activeIdx, count] = await Promise.all([cookies(), getActiveIndex(), getCount()]);
263
+ if (count === 0) return null;
264
+ if (activeIdx >= 1 && activeIdx <= count) {
265
+ const slot = readSlot(cookieStore, activeIdx);
266
+ if (slot) return slot;
267
+ }
268
+ return readSlot(cookieStore, 1);
269
+ }
270
+ async function getAccessToken() {
271
+ const active = await getActiveAccount();
272
+ return active?.accessToken ?? null;
273
+ }
274
+ async function getRefreshToken() {
275
+ const active = await getActiveAccount();
276
+ return active?.refreshToken ?? null;
277
+ }
278
+ async function getSession() {
279
+ const active = await getActiveAccount();
280
+ if (!active) return null;
281
+ return {
282
+ accessToken: active.accessToken,
283
+ refreshToken: active.refreshToken,
284
+ idToken: active.idToken,
285
+ sessionId: active.sessionId
286
+ };
287
+ }
288
+ async function getUser() {
289
+ const active = await getActiveAccount();
290
+ return active?.user ?? null;
291
+ }
292
+ async function getOrganizations() {
293
+ const cookieStore = await cookies();
294
+ const currentOrgId = cookieStore.get(cn.activeOrganizationId)?.value ?? null;
295
+ try {
296
+ const data = await apiRequest("/organizations");
297
+ const organizations = Array.isArray(data) ? data : data.organizations;
298
+ return { organizations, currentOrgId };
299
+ } catch {
300
+ return { organizations: [], currentOrgId };
301
+ }
302
+ }
303
+ function clearPublicTokenCookie(cookieStore) {
304
+ if (cookieStore.get(cn.tokenPub)?.value) {
305
+ cookieStore.set(cn.tokenPub, "", { httpOnly: false, maxAge: 0, path: "/" });
306
+ }
307
+ }
308
+ async function saveAccountToList(tokens) {
309
+ const account = parseAccountFromJwt(tokens);
310
+ if (!account) return;
311
+ const cookieStore = await cookies();
312
+ const opts = { ...getCookieOptions(config), maxAge: 60 * 60 * 24 * 30 };
313
+ const count = await getCount();
314
+ let targetSlot = 0;
315
+ for (let i = 1; i <= count; i++) {
316
+ const existing = readSlot(cookieStore, i);
317
+ if (existing?.id === account.id) {
318
+ targetSlot = i;
319
+ break;
320
+ }
321
+ }
322
+ if (targetSlot === 0) {
323
+ if (count >= MAX_ACCOUNTS) return;
324
+ targetSlot = count + 1;
325
+ cookieStore.set(cn.count, String(targetSlot), opts);
326
+ }
327
+ writeSlot(cookieStore, targetSlot, account);
328
+ cookieStore.set(cn.active, String(targetSlot), opts);
329
+ clearPublicTokenCookie(cookieStore);
330
+ }
331
+ const setSessionCookies = saveAccountToList;
332
+ async function switchAccount(accountId) {
333
+ const [cookieStore, count] = await Promise.all([cookies(), getCount()]);
334
+ const opts = { ...getCookieOptions(config), maxAge: 60 * 60 * 24 * 30 };
335
+ for (let i = 1; i <= count; i++) {
336
+ const slot = readSlot(cookieStore, i);
337
+ if (slot && (slot.id === accountId || slot.sessionId === accountId)) {
338
+ cookieStore.set(cn.active, String(i), opts);
339
+ clearPublicTokenCookie(cookieStore);
340
+ return true;
341
+ }
342
+ }
343
+ return false;
344
+ }
345
+ async function removeActiveAccount() {
346
+ const [cookieStore, activeIdx, count] = await Promise.all([cookies(), getActiveIndex(), getCount()]);
347
+ const opts = { ...getCookieOptions(config), maxAge: 60 * 60 * 24 * 30 };
348
+ const clear = { ...getCookieOptions(config), maxAge: 0 };
349
+ if (count === 0) return;
350
+ const remaining = [];
351
+ for (let i = 1; i <= count; i++) {
352
+ if (i === activeIdx) continue;
353
+ const slot = readSlot(cookieStore, i);
354
+ if (slot) remaining.push(slot);
355
+ }
356
+ for (let i = 1; i <= count; i++) {
357
+ clearSlot(cookieStore, i);
358
+ }
359
+ if (remaining.length === 0) {
360
+ cookieStore.set(cn.count, "0", clear);
361
+ cookieStore.set(cn.active, "0", clear);
362
+ await clearPublicTokenCookie(cookieStore);
363
+ return;
364
+ }
365
+ for (let i = 0; i < remaining.length; i++) {
366
+ await writeSlot(cookieStore, i + 1, remaining[i]);
367
+ }
368
+ cookieStore.set(cn.count, String(remaining.length), opts);
369
+ cookieStore.set(cn.active, "1", opts);
370
+ clearPublicTokenCookie(cookieStore);
371
+ }
372
+ async function clearAllSessionCookies() {
373
+ const [cookieStore, count] = await Promise.all([cookies(), getCount()]);
374
+ const clear = { ...getCookieOptions(config), maxAge: 0 };
375
+ for (let i = 1; i <= count; i++) {
376
+ clearSlot(cookieStore, i);
377
+ }
378
+ cookieStore.set(cn.count, "0", clear);
379
+ cookieStore.set(cn.active, "0", clear);
380
+ clearPublicTokenCookie(cookieStore);
381
+ }
382
+ async function tryRefreshTokens() {
383
+ const refreshToken = await getRefreshToken();
384
+ if (!refreshToken || !config.clientId) return null;
385
+ const basicAuth = Buffer.from(`${config.clientId}:${config.clientSecret ?? ""}`).toString("base64");
386
+ const response = await fetch(createUrl(config.issuer, "/auth/refresh"), {
387
+ method: "POST",
388
+ headers: {
389
+ "content-type": "application/x-www-form-urlencoded",
390
+ authorization: `Basic ${basicAuth}`
391
+ },
392
+ body: new URLSearchParams({
393
+ grant_type: "refresh_token",
394
+ refresh_token: refreshToken
395
+ }),
396
+ cache: "no-store"
397
+ });
398
+ if (!response.ok) return null;
399
+ const data = await response.json().catch(() => null);
400
+ if (!data) return null;
401
+ const tokens = tokenFromResponse(data);
402
+ if (!tokens.accessToken) return null;
403
+ await saveAccountToList(tokens);
404
+ return tokens.accessToken;
405
+ }
406
+ async function apiRequest(path, init = {}) {
407
+ const accessToken = await getAccessToken();
408
+ const requestHeaders = new Headers(init.headers);
409
+ if (accessToken) requestHeaders.set("authorization", `Bearer ${accessToken}`);
410
+ applyServerClientCredentials(requestHeaders, config);
411
+ if (init.body !== void 0 && !requestHeaders.has("content-type")) {
412
+ requestHeaders.set("content-type", "application/json");
413
+ }
414
+ const response = await fetch(createUrl(config.issuer, path), {
415
+ ...init,
416
+ headers: requestHeaders,
417
+ cache: "no-store"
418
+ });
419
+ if (response.status === 401) {
420
+ const newAccessToken = await tryRefreshTokens();
421
+ if (newAccessToken) {
422
+ const retryHeaders = new Headers(init.headers);
423
+ retryHeaders.set("authorization", `Bearer ${newAccessToken}`);
424
+ applyServerClientCredentials(retryHeaders, config);
425
+ if (init.body !== void 0 && !retryHeaders.has("content-type")) {
426
+ retryHeaders.set("content-type", "application/json");
427
+ }
428
+ const retryResponse = await fetch(createUrl(config.issuer, path), {
429
+ ...init,
430
+ headers: retryHeaders,
431
+ cache: "no-store"
432
+ });
433
+ if (!retryResponse.ok) {
434
+ const error = await retryResponse.json().catch(() => null);
435
+ throw new Error(error?.error_description ?? error?.error ?? error?.message ?? `Auth API error ${retryResponse.status}`);
436
+ }
437
+ if (retryResponse.status === 204) return null;
438
+ return await retryResponse.json();
439
+ }
440
+ }
441
+ if (!response.ok) {
442
+ const error = await response.json().catch(() => null);
443
+ throw new Error(error?.error_description ?? error?.error ?? error?.message ?? `Auth API error ${response.status}`);
444
+ }
445
+ if (response.status === 204) return null;
446
+ return await response.json();
447
+ }
448
+ function createApiClient() {
449
+ return {
450
+ get: (path) => apiRequest(path),
451
+ post: (path, body) => apiRequest(path, { method: "POST", body: body === void 0 ? void 0 : JSON.stringify(body) }),
452
+ patch: (path, body) => apiRequest(path, { method: "PATCH", body: body === void 0 ? void 0 : JSON.stringify(body) }),
453
+ put: (path, body) => apiRequest(path, { method: "PUT", body: body === void 0 ? void 0 : JSON.stringify(body) }),
454
+ delete: (path) => apiRequest(path, { method: "DELETE" })
455
+ };
456
+ }
457
+ async function requireUser(options = {}) {
458
+ const accessToken = await getAccessToken();
459
+ if (!accessToken) {
460
+ redirect(options.redirectTo ?? "/");
461
+ }
462
+ return accessToken;
463
+ }
464
+ function createAuthorizeRedirect(request, mode) {
465
+ const redirectTo = request.nextUrl.searchParams.get("redirectTo") ?? config.afterSignInRedirectTo ?? "/";
466
+ const flowId = request.nextUrl.searchParams.get("flow_id");
467
+ const url = new URL(createUrl(config.issuer, mode === "google" ? "/auth/google" : "/authorize"));
468
+ if (!config.clientId) {
469
+ return NextResponse.json(
470
+ { error: "missing_client_id", message: "Missing GEEKAPPS_AUTH_CLIENT_ID or clientId." },
471
+ { status: 500 }
472
+ );
473
+ }
474
+ url.searchParams.set("client_id", config.clientId);
475
+ url.searchParams.set("redirect_uri", resolveRedirectUri(config, request));
476
+ url.searchParams.set("response_type", "code");
477
+ url.searchParams.set("scope", (config.scopes ?? ["openid", "profile", "email"]).join(" "));
478
+ url.searchParams.set("state", Buffer.from(JSON.stringify({ redirectTo, mode })).toString("base64url"));
479
+ if (mode === "register") url.searchParams.set("screen_hint", "signup");
480
+ if (mode === "add-account") url.searchParams.set("prompt", "login");
481
+ if (flowId) url.searchParams.set("flow_id", flowId);
482
+ return NextResponse.redirect(url);
483
+ }
484
+ async function exchangeCode(request) {
485
+ const code = request.nextUrl.searchParams.get("code");
486
+ const state = request.nextUrl.searchParams.get("state");
487
+ if (!code) return NextResponse.redirect(new URL("/error?error=missing_code", request.url));
488
+ if (!config.clientId) {
489
+ return NextResponse.json(
490
+ { error: "missing_client_id", message: "Missing GEEKAPPS_AUTH_CLIENT_ID or clientId." },
491
+ { status: 500 }
492
+ );
493
+ }
494
+ const basicAuth = Buffer.from(
495
+ `${config.clientId}:${config.clientSecret ?? ""}`
496
+ ).toString("base64");
497
+ const response = await fetch(createUrl(config.issuer, "/auth/token"), {
498
+ method: "POST",
499
+ headers: {
500
+ "content-type": "application/x-www-form-urlencoded",
501
+ authorization: `Basic ${basicAuth}`
502
+ },
503
+ body: new URLSearchParams({
504
+ grant_type: "authorization_code",
505
+ code,
506
+ redirect_uri: resolveRedirectUri(config, request)
507
+ }),
508
+ cache: "no-store"
509
+ });
510
+ const data = await response.json().catch(() => ({}));
511
+ if (!response.ok) {
512
+ return NextResponse.json(data, { status: response.status });
513
+ }
514
+ await saveAccountToList(tokenFromResponse(data));
515
+ let redirectTo = config.afterSignInRedirectTo ?? "/";
516
+ if (state) {
517
+ try {
518
+ const parsed = JSON.parse(Buffer.from(state, "base64url").toString("utf8"));
519
+ redirectTo = parsed.redirectTo ?? redirectTo;
520
+ } catch {
521
+ }
522
+ }
523
+ return NextResponse.redirect(new URL(redirectTo, request.url));
524
+ }
525
+ function mapLocalPathToApiPath(route) {
526
+ if (route === "/me") return "/auth/me";
527
+ if (route === "/accounts/switch") return "/auth/accounts/switch";
528
+ if (route === "/organizations/active") return "/auth/organizations/active";
529
+ if (route === "/permissions/check") return "/auth/permissions/check";
530
+ if (route === "/login/password") return "/auth/login/password";
531
+ if (route === "/register/password") return "/auth/register/password";
532
+ if (route === "/passwordless/link") return "/auth/passwordless/link";
533
+ return route;
534
+ }
535
+ async function handleAccountsLocal() {
536
+ const [accounts, activeIdx] = await Promise.all([getAccountsList(), getActiveIndex()]);
537
+ return NextResponse.json({
538
+ accounts: accounts.map((a, i) => ({
539
+ id: a.id,
540
+ sessionId: a.sessionId ?? a.id,
541
+ isActive: i + 1 === activeIdx,
542
+ user: {
543
+ id: a.user.sub,
544
+ name: a.user.name ?? null,
545
+ email: a.user.email ?? null,
546
+ imageUrl: a.user.picture ?? null,
547
+ phone: a.user.phone ?? null
548
+ }
549
+ }))
550
+ });
551
+ }
552
+ async function handleOrganizationsLocal() {
553
+ const cookieStore = await cookies();
554
+ const activeOrgId = cookieStore.get(cn.activeOrganizationId)?.value ?? null;
555
+ try {
556
+ const data = await apiRequest("/auth/organizations");
557
+ const organizations = Array.isArray(data) ? data : data.organizations;
558
+ return NextResponse.json({ organizations, currentOrgId: activeOrgId });
559
+ } catch {
560
+ return NextResponse.json({ organizations: [], currentOrgId: activeOrgId });
561
+ }
562
+ }
563
+ async function handleSwitchAccountLocal(request) {
564
+ const body = await readBody(request);
565
+ const targetId = body && typeof body === "object" ? String(
566
+ body.accountId ?? body.sessionId ?? ""
567
+ ) : "";
568
+ if (!targetId) {
569
+ return NextResponse.json({ error: "accountId required" }, { status: 400 });
570
+ }
571
+ const switched = await switchAccount(targetId);
572
+ if (!switched) {
573
+ return NextResponse.json({ error: "Account not found" }, { status: 404 });
574
+ }
575
+ return NextResponse.json({ ok: true });
576
+ }
577
+ async function proxy(request, route) {
578
+ const accessToken = await getAccessToken();
579
+ const body = ["GET", "HEAD"].includes(request.method) ? void 0 : await readBody(request);
580
+ const url = appendSearchParams(createUrl(config.issuer, mapLocalPathToApiPath(route)), request);
581
+ const requestHeaders = new Headers();
582
+ if (accessToken) requestHeaders.set("authorization", `Bearer ${accessToken}`);
583
+ applyServerClientCredentials(requestHeaders, config);
584
+ if (body !== void 0) requestHeaders.set("content-type", "application/json");
585
+ const response = await fetch(url, {
586
+ method: request.method,
587
+ headers: requestHeaders,
588
+ body: body === void 0 ? void 0 : typeof body === "string" ? body : JSON.stringify(body),
589
+ cache: "no-store"
590
+ });
591
+ const data = isJsonResponse(response) ? await response.json().catch(() => null) : null;
592
+ if (data) {
593
+ const tokens = tokenFromResponse(data);
594
+ if (tokens.accessToken || tokens.refreshToken || tokens.idToken || tokens.sessionId) {
595
+ await saveAccountToList(tokens);
596
+ }
597
+ return NextResponse.json(data, { status: response.status });
598
+ }
599
+ return responseFromApi(response);
600
+ }
601
+ async function handle(request) {
602
+ const route = routeFromRequest(request, config.basePath);
603
+ if (request.method === "GET" && route === "/sign-in") return createAuthorizeRedirect(request, "login");
604
+ if (request.method === "GET" && route === "/sign-up") return createAuthorizeRedirect(request, "register");
605
+ if (request.method === "GET" && route === "/add-account") return createAuthorizeRedirect(request, "add-account");
606
+ if (request.method === "GET" && route === "/login/google") return createAuthorizeRedirect(request, "google");
607
+ if (request.method === "GET" && route === "/callback") return exchangeCode(request);
608
+ if (request.method === "GET" && route === "/token") {
609
+ const accessToken = await getAccessToken();
610
+ if (!accessToken) return NextResponse.json({ error: "missing_token" }, { status: 401 });
611
+ return NextResponse.json({ accessToken });
612
+ }
613
+ if (request.method === "GET" && route === "/accounts") return handleAccountsLocal();
614
+ if (request.method === "POST" && route === "/accounts/switch") return handleSwitchAccountLocal(request);
615
+ if (request.method === "GET" && route === "/organizations") return handleOrganizationsLocal();
616
+ if (request.method === "POST" && route === "/organizations") {
617
+ const accessToken = await getAccessToken();
618
+ const body = await readBody(request);
619
+ if (body && accessToken && !body.applicationId) {
620
+ const claims = decodeJwtPayload(accessToken);
621
+ const applicationId = (typeof claims?.application_id === "string" ? claims.application_id : null) ?? (typeof claims?.client_id === "string" ? claims.client_id : null) ?? (typeof claims?.applicationId === "string" ? claims.applicationId : null);
622
+ if (applicationId) body.applicationId = applicationId;
623
+ }
624
+ const url = createUrl(config.issuer, "/organizations");
625
+ const requestHeaders = new Headers();
626
+ if (accessToken) requestHeaders.set("authorization", `Bearer ${accessToken}`);
627
+ applyServerClientCredentials(requestHeaders, config);
628
+ if (body !== void 0) requestHeaders.set("content-type", "application/json");
629
+ const response = await fetch(url, {
630
+ method: "POST",
631
+ headers: requestHeaders,
632
+ body: JSON.stringify(body),
633
+ cache: "no-store"
634
+ });
635
+ return responseFromApi(response);
636
+ }
637
+ if (request.method === "POST" && route === "/sign-out") {
638
+ await apiRequest("/auth/sign-out", { method: "POST" }).catch(() => null);
639
+ await removeActiveAccount();
640
+ return NextResponse.json({ ok: true });
641
+ }
642
+ if (request.method === "POST" && route === "/sign-out-all") {
643
+ await apiRequest("/auth/sign-out-all", { method: "POST" }).catch(() => null);
644
+ await clearAllSessionCookies();
645
+ return NextResponse.json({ ok: true });
646
+ }
647
+ if (request.method === "POST" && route === "/organizations/active") {
648
+ const body = await readBody(request);
649
+ const organizationId = body && typeof body === "object" && "organizationId" in body ? body.organizationId : null;
650
+ const response = NextResponse.json({ ok: true });
651
+ if (typeof organizationId === "string") {
652
+ const cookieOpts = {
653
+ httpOnly: true,
654
+ sameSite: "lax",
655
+ path: "/",
656
+ secure: process.env.NODE_ENV === "production",
657
+ maxAge: 60 * 60 * 24 * 7
658
+ };
659
+ response.cookies.set(cn.activeOrganizationId, organizationId, cookieOpts);
660
+ }
661
+ return response;
662
+ }
663
+ return proxy(request, route);
664
+ }
665
+ return {
666
+ handlers: {
667
+ GET: handle,
668
+ POST: handle,
669
+ PATCH: handle,
670
+ DELETE: handle
671
+ },
672
+ GetSession: getSession,
673
+ GetUser: getUser,
674
+ GetOrganizations: getOrganizations,
675
+ SaveSession: saveAccountToList,
676
+ GetAccessToken: getAccessToken,
677
+ GetRefreshToken: getRefreshToken,
678
+ TryRefreshTokens: tryRefreshTokens,
679
+ RequireUser: requireUser,
680
+ CreateAuthApiClient: createApiClient,
681
+ cookiePrefix: config.cookiePrefix,
682
+ VerifyToken: (token) => verifyToken(token, config.issuer),
683
+ SignInUrl: async (redirectTo) => {
684
+ const headerStore = await headers();
685
+ const host = headerStore.get("host") ?? "localhost";
686
+ const protocol = headerStore.get("x-forwarded-proto") ?? "http";
687
+ const url = new URL(`${protocol}://${host}${config.basePath}/sign-in`);
688
+ if (redirectTo) url.searchParams.set("redirectTo", redirectTo);
689
+ return url.toString();
690
+ }
691
+ };
692
+ }
693
+ function handlers(options) {
694
+ return configureGeekappsAuth(options).handlers;
695
+ }
696
+ export {
697
+ configureGeekappsAuth,
698
+ handlers,
699
+ verifyToken
700
+ };
701
+ //# sourceMappingURL=server.js.map