@ghostly-solutions/auth 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/next.js ADDED
@@ -0,0 +1,899 @@
1
+ // src/constants/auth-endpoints.ts
2
+ var authApiPrefix = "/v1/auth";
3
+ var authEndpoints = {
4
+ loginStart: `${authApiPrefix}/keycloak/login`,
5
+ validateKeycloakToken: `${authApiPrefix}/keycloak/validate`,
6
+ session: `${authApiPrefix}/me`,
7
+ logout: `${authApiPrefix}/logout`
8
+ };
9
+
10
+ // src/constants/http-status.ts
11
+ var httpStatus = {
12
+ ok: 200,
13
+ found: 302,
14
+ noContent: 204,
15
+ badRequest: 400,
16
+ unauthorized: 401
17
+ };
18
+
19
+ // src/errors/auth-sdk-error.ts
20
+ var AuthSdkError = class extends Error {
21
+ code;
22
+ details;
23
+ status;
24
+ constructor(payload) {
25
+ super(payload.message);
26
+ this.name = "AuthSdkError";
27
+ this.code = payload.code;
28
+ this.details = payload.details;
29
+ this.status = payload.status;
30
+ }
31
+ };
32
+
33
+ // src/types/auth-error-code.ts
34
+ var authErrorCode = {
35
+ callbackMissingToken: "callback_missing_token",
36
+ callbackInvalidToken: "callback_invalid_token",
37
+ callbackValidationFailed: "callback_validation_failed",
38
+ unauthorized: "unauthorized",
39
+ networkError: "network_error",
40
+ apiError: "api_error",
41
+ broadcastChannelUnsupported: "broadcast_channel_unsupported",
42
+ serverOriginResolutionFailed: "server_origin_resolution_failed"
43
+ };
44
+
45
+ // src/core/object-guards.ts
46
+ function isObjectRecord(value) {
47
+ return typeof value === "object" && value !== null;
48
+ }
49
+ function isStringValue(value) {
50
+ return typeof value === "string";
51
+ }
52
+
53
+ // src/core/session-parser.ts
54
+ function isStringArray(value) {
55
+ return Array.isArray(value) && value.every((entry) => isStringValue(entry));
56
+ }
57
+ function isGhostlySession(value) {
58
+ if (!isObjectRecord(value)) {
59
+ return false;
60
+ }
61
+ return isStringValue(value.id) && isStringValue(value.username) && (value.firstName === null || isStringValue(value.firstName)) && (value.lastName === null || isStringValue(value.lastName)) && isStringValue(value.email) && isStringValue(value.role) && isStringArray(value.permissions);
62
+ }
63
+
64
+ // src/adapters/next/server-session.ts
65
+ var hostHeaderName = "x-forwarded-host";
66
+ var fallbackHostHeaderName = "host";
67
+ var forwardedProtoHeaderName = "x-forwarded-proto";
68
+ var defaultProtocol = "https";
69
+ var protocolSeparator = "://";
70
+ var forwardedSessionHeaderNames = ["cookie", "authorization", "x-request-id"];
71
+ function resolveServerOrigin(options) {
72
+ const host = options.headers.get(hostHeaderName) ?? options.headers.get(fallbackHostHeaderName);
73
+ if (!host) {
74
+ throw new AuthSdkError({
75
+ code: authErrorCode.serverOriginResolutionFailed,
76
+ details: null,
77
+ message: "Cannot resolve server origin from request headers.",
78
+ status: null
79
+ });
80
+ }
81
+ const protocol = options.protocol ?? options.headers.get(forwardedProtoHeaderName) ?? defaultProtocol;
82
+ return `${protocol}${protocolSeparator}${host}`;
83
+ }
84
+ function forwardSessionHeaders(headers) {
85
+ const forwarded = new Headers();
86
+ for (const headerName of forwardedSessionHeaderNames) {
87
+ const value = headers.get(headerName);
88
+ if (value) {
89
+ forwarded.set(headerName, value);
90
+ }
91
+ }
92
+ return forwarded;
93
+ }
94
+ function parseSessionPayload(payload) {
95
+ if (payload === null) {
96
+ return null;
97
+ }
98
+ if (!isGhostlySession(payload)) {
99
+ throw new AuthSdkError({
100
+ code: authErrorCode.apiError,
101
+ details: payload,
102
+ message: "Session payload has invalid shape.",
103
+ status: null
104
+ });
105
+ }
106
+ return payload;
107
+ }
108
+ async function getServerSession(options) {
109
+ const fetchImplementation = options.fetchImplementation ?? fetch;
110
+ const origin = resolveServerOrigin(options);
111
+ const response = await fetchImplementation(`${origin}${authEndpoints.session}`, {
112
+ cache: "no-store",
113
+ credentials: "include",
114
+ headers: forwardSessionHeaders(options.headers),
115
+ method: "GET"
116
+ });
117
+ if (response.status === httpStatus.ok) {
118
+ const payload = await response.json();
119
+ return parseSessionPayload(payload);
120
+ }
121
+ if (response.status === httpStatus.unauthorized) {
122
+ throw new AuthSdkError({
123
+ code: authErrorCode.unauthorized,
124
+ details: null,
125
+ message: "Unauthorized server session request.",
126
+ status: response.status
127
+ });
128
+ }
129
+ throw new AuthSdkError({
130
+ code: authErrorCode.apiError,
131
+ details: null,
132
+ message: "Unexpected server session response.",
133
+ status: response.status
134
+ });
135
+ }
136
+ async function requireServerSession(options) {
137
+ const session = await getServerSession(options);
138
+ if (session) {
139
+ return session;
140
+ }
141
+ throw new AuthSdkError({
142
+ code: authErrorCode.unauthorized,
143
+ details: null,
144
+ message: "Authenticated session is required.",
145
+ status: httpStatus.unauthorized
146
+ });
147
+ }
148
+
149
+ // src/adapters/next/auth-kit.ts
150
+ var defaultCallbackToken = "mock-keycloak-token";
151
+ var defaultCookieName = "gs_auth_session";
152
+ var defaultCookieValue = "mock-session-super-admin";
153
+ var defaultFrontendCallbackPath = "/auth/callback";
154
+ var callbackCodePrefix = "mock_code_";
155
+ var callbackStatePrefix = "mock_state_";
156
+ var clearCookieDate = "Thu, 01 Jan 1970 00:00:00 GMT";
157
+ var proxyForwardedHeaderNames = [
158
+ "accept-language",
159
+ "authorization",
160
+ "cookie",
161
+ "content-type",
162
+ "x-request-id"
163
+ ];
164
+ function defaultMockSessionFactory() {
165
+ return {
166
+ id: "123",
167
+ username: "john_doe",
168
+ firstName: "John",
169
+ lastName: "Doe",
170
+ email: "john.doe@ghostlysolutions.ae",
171
+ role: "superAdmin",
172
+ permissions: [
173
+ "users:view",
174
+ "tasks:view",
175
+ "analytics:view",
176
+ "settings:view",
177
+ "audit-logs:view",
178
+ "admins:view"
179
+ ]
180
+ };
181
+ }
182
+ function createErrorPayload(code, message, details = null) {
183
+ return { code, message, details };
184
+ }
185
+ function toJsonResponse(payload, status) {
186
+ return new Response(JSON.stringify(payload), {
187
+ status,
188
+ headers: {
189
+ "content-type": "application/json"
190
+ }
191
+ });
192
+ }
193
+ function parseCookieValue(cookieHeader, cookieName) {
194
+ if (!cookieHeader) {
195
+ return null;
196
+ }
197
+ const prefix = `${cookieName}=`;
198
+ const pairs = cookieHeader.split(";").map((pair) => pair.trim());
199
+ const match = pairs.find((pair) => pair.startsWith(prefix));
200
+ return match ? match.slice(prefix.length) : null;
201
+ }
202
+ function shouldForwardBody(method) {
203
+ const normalized = method.toUpperCase();
204
+ return normalized !== "GET" && normalized !== "HEAD";
205
+ }
206
+ function resolveSecureCookie(requestUrl, secureMode) {
207
+ if (typeof secureMode === "boolean") {
208
+ return secureMode;
209
+ }
210
+ if (requestUrl.protocol !== "https:") {
211
+ return false;
212
+ }
213
+ const hostname = requestUrl.hostname.toLowerCase();
214
+ return hostname !== "localhost" && hostname !== "127.0.0.1";
215
+ }
216
+ function toSetCookieHeader(options) {
217
+ const attributes = ["Path=/", "HttpOnly", "SameSite=Lax"];
218
+ if (options.secure) {
219
+ attributes.push("Secure");
220
+ }
221
+ if (options.clear) {
222
+ attributes.push("Max-Age=0");
223
+ attributes.push(`Expires=${clearCookieDate}`);
224
+ }
225
+ return `${options.cookieName}=${options.cookieValue}; ${attributes.join("; ")}`;
226
+ }
227
+ async function proxyRequest(baseUrl, request2) {
228
+ const incomingUrl = new URL(request2.url);
229
+ const targetUrl = new URL(incomingUrl.pathname + incomingUrl.search, baseUrl);
230
+ const proxyHeaders = new Headers();
231
+ for (const headerName of proxyForwardedHeaderNames) {
232
+ const value = request2.headers.get(headerName);
233
+ if (value) {
234
+ proxyHeaders.set(headerName, value);
235
+ }
236
+ }
237
+ const body = shouldForwardBody(request2.method) ? await request2.text() : void 0;
238
+ const upstreamResponse = await fetch(targetUrl, {
239
+ method: request2.method,
240
+ headers: proxyHeaders,
241
+ cache: "no-store",
242
+ redirect: "manual",
243
+ ...body !== void 0 ? { body } : {}
244
+ });
245
+ const responseHeaders = new Headers();
246
+ for (const [headerName, headerValue] of upstreamResponse.headers.entries()) {
247
+ responseHeaders.set(headerName, headerValue);
248
+ }
249
+ return new Response(upstreamResponse.body, {
250
+ status: upstreamResponse.status,
251
+ headers: responseHeaders
252
+ });
253
+ }
254
+ async function resolveNextHeaders() {
255
+ try {
256
+ const importNextHeaders = new Function("return import('next/headers')");
257
+ const nextHeaders = await importNextHeaders();
258
+ const resolved = await nextHeaders.headers();
259
+ return resolved;
260
+ } catch (error) {
261
+ throw new AuthSdkError({
262
+ code: authErrorCode.serverOriginResolutionFailed,
263
+ details: error,
264
+ message: "Unable to resolve Next request headers.",
265
+ status: null
266
+ });
267
+ }
268
+ }
269
+ function resolveNextProtocol(headers, explicitProtocol) {
270
+ if (explicitProtocol) {
271
+ return explicitProtocol;
272
+ }
273
+ const forwarded = headers.get("x-forwarded-proto");
274
+ if (forwarded) {
275
+ return forwarded;
276
+ }
277
+ const host = headers.get("x-forwarded-host") ?? headers.get("host");
278
+ if (!host) {
279
+ return "https";
280
+ }
281
+ const normalizedHost = host.toLowerCase();
282
+ return normalizedHost.includes("localhost") || normalizedHost.includes("127.0.0.1") ? "http" : "https";
283
+ }
284
+ function normalizeMockToken(payload) {
285
+ if (typeof payload !== "object" || payload === null || Array.isArray(payload)) {
286
+ return null;
287
+ }
288
+ const recordPayload = payload;
289
+ const keys = Object.keys(recordPayload);
290
+ if (keys.length !== 1 || !keys.includes("token")) {
291
+ return null;
292
+ }
293
+ const tokenValue = recordPayload.token;
294
+ if (typeof tokenValue !== "string") {
295
+ return null;
296
+ }
297
+ const normalized = tokenValue.trim();
298
+ return normalized.length > 0 ? normalized : null;
299
+ }
300
+ async function getNextServerSession(options = {}) {
301
+ const headers = options.headers ?? await resolveNextHeaders();
302
+ const protocol = resolveNextProtocol(headers, options.protocol);
303
+ return getServerSession({
304
+ headers,
305
+ protocol,
306
+ ...options.fetchImplementation ? { fetchImplementation: options.fetchImplementation } : {}
307
+ });
308
+ }
309
+ async function tryGetNextServerSession(options = {}) {
310
+ try {
311
+ return await getNextServerSession(options);
312
+ } catch {
313
+ return null;
314
+ }
315
+ }
316
+ async function requireNextServerSession(options = {}) {
317
+ const session = await getNextServerSession(options);
318
+ if (session) {
319
+ return session;
320
+ }
321
+ throw new AuthSdkError({
322
+ code: authErrorCode.unauthorized,
323
+ details: null,
324
+ message: "Authenticated session is required.",
325
+ status: httpStatus.unauthorized
326
+ });
327
+ }
328
+ function createNextAuthRouteHandlers(options) {
329
+ const callbackToken = options.mock?.callbackToken ?? defaultCallbackToken;
330
+ const cookieName = options.mock?.cookieName ?? defaultCookieName;
331
+ const cookieValue = options.mock?.cookieValue ?? defaultCookieValue;
332
+ const cookieSecure = options.mock?.cookieSecure ?? "auto";
333
+ const frontendCallbackPath = options.mock?.frontendCallbackPath ?? defaultFrontendCallbackPath;
334
+ const createSession = options.mock?.createSession ?? defaultMockSessionFactory;
335
+ const proxyOptions = options.proxy;
336
+ if (options.mode === "proxy" && !proxyOptions?.baseUrl) {
337
+ throw new AuthSdkError({
338
+ code: authErrorCode.apiError,
339
+ details: null,
340
+ message: "proxy.baseUrl is required in proxy mode.",
341
+ status: null
342
+ });
343
+ }
344
+ const proxyIfNeeded = async (request2) => {
345
+ if (options.mode !== "proxy") {
346
+ return null;
347
+ }
348
+ return proxyRequest(proxyOptions?.baseUrl ?? "", request2);
349
+ };
350
+ return {
351
+ keycloakLoginGet: async (request2) => {
352
+ const proxied = await proxyIfNeeded(request2);
353
+ if (proxied) {
354
+ return proxied;
355
+ }
356
+ const code = `${callbackCodePrefix}${crypto.randomUUID()}`;
357
+ const state = `${callbackStatePrefix}${crypto.randomUUID()}`;
358
+ const redirectTarget = `${authEndpoints.loginStart.replace("/login", "/callback")}?code=${encodeURIComponent(code)}&state=${encodeURIComponent(state)}`;
359
+ return new Response(null, {
360
+ status: httpStatus.found,
361
+ headers: {
362
+ location: redirectTarget
363
+ }
364
+ });
365
+ },
366
+ keycloakCallbackGet: async (request2) => {
367
+ const proxied = await proxyIfNeeded(request2);
368
+ if (proxied) {
369
+ return proxied;
370
+ }
371
+ const requestUrl = new URL(request2.url);
372
+ const code = requestUrl.searchParams.get("code")?.trim();
373
+ const state = requestUrl.searchParams.get("state")?.trim();
374
+ if (!(code && state)) {
375
+ return toJsonResponse(
376
+ createErrorPayload("bad_request", "code and state are required."),
377
+ httpStatus.badRequest
378
+ );
379
+ }
380
+ const callbackUrl = `${frontendCallbackPath}?token=${encodeURIComponent(callbackToken)}`;
381
+ return new Response(null, {
382
+ status: httpStatus.found,
383
+ headers: {
384
+ location: callbackUrl
385
+ }
386
+ });
387
+ },
388
+ keycloakValidatePost: async (request2) => {
389
+ const proxied = await proxyIfNeeded(request2);
390
+ if (proxied) {
391
+ return proxied;
392
+ }
393
+ let payload;
394
+ try {
395
+ payload = await request2.json();
396
+ } catch {
397
+ return toJsonResponse(
398
+ createErrorPayload("bad_request", "Request payload must be valid JSON."),
399
+ httpStatus.badRequest
400
+ );
401
+ }
402
+ const token = normalizeMockToken(payload);
403
+ if (!token) {
404
+ return toJsonResponse(
405
+ createErrorPayload(
406
+ "bad_request",
407
+ "Request payload must contain only non-empty token field."
408
+ ),
409
+ httpStatus.badRequest
410
+ );
411
+ }
412
+ if (token !== callbackToken) {
413
+ return toJsonResponse(
414
+ createErrorPayload("unauthorized", "Callback token is invalid or expired."),
415
+ httpStatus.unauthorized
416
+ );
417
+ }
418
+ const session = createSession();
419
+ const responsePayload = { session };
420
+ const secure = resolveSecureCookie(new URL(request2.url), cookieSecure);
421
+ return new Response(JSON.stringify(responsePayload), {
422
+ status: httpStatus.ok,
423
+ headers: {
424
+ "content-type": "application/json",
425
+ "set-cookie": toSetCookieHeader({
426
+ cookieName,
427
+ cookieValue,
428
+ secure
429
+ })
430
+ }
431
+ });
432
+ },
433
+ meGet: async (request2) => {
434
+ const proxied = await proxyIfNeeded(request2);
435
+ if (proxied) {
436
+ return proxied;
437
+ }
438
+ const cookie = request2.headers.get("cookie");
439
+ const session = parseCookieValue(cookie, cookieName) === cookieValue ? createSession() : null;
440
+ return toJsonResponse(session, httpStatus.ok);
441
+ },
442
+ logoutPost: async (request2) => {
443
+ const proxied = await proxyIfNeeded(request2);
444
+ if (proxied) {
445
+ return proxied;
446
+ }
447
+ const secure = resolveSecureCookie(new URL(request2.url), cookieSecure);
448
+ return new Response(null, {
449
+ status: httpStatus.noContent,
450
+ headers: {
451
+ "set-cookie": toSetCookieHeader({
452
+ cookieName,
453
+ cookieValue: "",
454
+ secure,
455
+ clear: true
456
+ })
457
+ }
458
+ });
459
+ }
460
+ };
461
+ }
462
+
463
+ // src/constants/auth-keys.ts
464
+ var authQueryKeys = {
465
+ token: "token"
466
+ };
467
+ var authStorageKeys = {
468
+ returnTo: "ghostly-auth:return-to"
469
+ };
470
+ var authBroadcast = {
471
+ channelName: "ghostly-auth-channel",
472
+ sessionUpdatedEvent: "session-updated"
473
+ };
474
+ var authRoutes = {
475
+ root: "/"};
476
+
477
+ // src/core/broadcast-sync.ts
478
+ function isSessionUpdatedMessage(value) {
479
+ if (!isObjectRecord(value)) {
480
+ return false;
481
+ }
482
+ if (!isStringValue(value.type)) {
483
+ return false;
484
+ }
485
+ if (value.type !== authBroadcast.sessionUpdatedEvent) {
486
+ return false;
487
+ }
488
+ return value.session === null || isGhostlySession(value.session);
489
+ }
490
+ function createUnsupportedBroadcastChannelError() {
491
+ return new AuthSdkError({
492
+ code: authErrorCode.broadcastChannelUnsupported,
493
+ details: null,
494
+ message: "BroadcastChannel is unavailable in this runtime.",
495
+ status: null
496
+ });
497
+ }
498
+ function createBroadcastSync(options) {
499
+ if (typeof BroadcastChannel === "undefined") {
500
+ throw createUnsupportedBroadcastChannelError();
501
+ }
502
+ const channel = new BroadcastChannel(authBroadcast.channelName);
503
+ const onMessage = (event) => {
504
+ const messageEvent = event;
505
+ if (!isSessionUpdatedMessage(messageEvent.data)) {
506
+ return;
507
+ }
508
+ options.onSessionUpdated(messageEvent.data.session);
509
+ };
510
+ channel.addEventListener("message", onMessage);
511
+ return {
512
+ close() {
513
+ channel.removeEventListener("message", onMessage);
514
+ channel.close();
515
+ },
516
+ publishSession(session) {
517
+ const payload = {
518
+ session,
519
+ type: authBroadcast.sessionUpdatedEvent
520
+ };
521
+ channel.postMessage(payload);
522
+ }
523
+ };
524
+ }
525
+
526
+ // src/core/callback-url.ts
527
+ function readCallbackToken(url) {
528
+ return url.searchParams.get(authQueryKeys.token);
529
+ }
530
+ function removeCallbackToken(url) {
531
+ const nextUrl = new URL(url.toString());
532
+ nextUrl.searchParams.delete(authQueryKeys.token);
533
+ return nextUrl;
534
+ }
535
+ function replaceBrowserHistory(url) {
536
+ window.history.replaceState(null, "", url.toString());
537
+ }
538
+
539
+ // src/core/http-client.ts
540
+ var jsonContentType = "application/json";
541
+ var jsonHeaderName = "content-type";
542
+ var includeCredentials = "include";
543
+ var noStoreCache = "no-store";
544
+ function toTypedValue(value) {
545
+ return value;
546
+ }
547
+ function mapHttpStatusToAuthErrorCode(status) {
548
+ if (status === httpStatus.unauthorized) {
549
+ return authErrorCode.unauthorized;
550
+ }
551
+ return authErrorCode.apiError;
552
+ }
553
+ async function parseJsonPayload(response) {
554
+ try {
555
+ return await response.json();
556
+ } catch {
557
+ return null;
558
+ }
559
+ }
560
+ async function parseErrorPayload(response) {
561
+ const payload = await parseJsonPayload(response);
562
+ if (!isObjectRecord(payload)) {
563
+ return {
564
+ code: null,
565
+ details: null,
566
+ message: null
567
+ };
568
+ }
569
+ const maybeCode = payload.code;
570
+ const maybeMessage = payload.message;
571
+ return {
572
+ code: isStringValue(maybeCode) ? maybeCode : null,
573
+ details: "details" in payload ? payload.details : null,
574
+ message: isStringValue(maybeMessage) ? maybeMessage : null
575
+ };
576
+ }
577
+ function buildApiErrorMessage(method, path) {
578
+ return `Auth API request failed: ${method} ${path}`;
579
+ }
580
+ function buildNetworkErrorMessage(method, path) {
581
+ return `Auth API network failure: ${method} ${path}`;
582
+ }
583
+ async function request(options) {
584
+ const expectedStatus = options.expectedStatus ?? httpStatus.ok;
585
+ const headers = new Headers();
586
+ const hasBody = typeof options.body !== "undefined";
587
+ if (hasBody) {
588
+ headers.set(jsonHeaderName, jsonContentType);
589
+ }
590
+ const requestInit = {
591
+ cache: noStoreCache,
592
+ credentials: includeCredentials,
593
+ headers,
594
+ method: options.method
595
+ };
596
+ if (hasBody) {
597
+ requestInit.body = JSON.stringify(options.body);
598
+ }
599
+ let response;
600
+ try {
601
+ response = await fetch(options.path, requestInit);
602
+ } catch (error) {
603
+ throw new AuthSdkError({
604
+ code: authErrorCode.networkError,
605
+ details: error,
606
+ message: buildNetworkErrorMessage(options.method, options.path),
607
+ status: null
608
+ });
609
+ }
610
+ if (response.status !== expectedStatus) {
611
+ const parsed = await parseErrorPayload(response);
612
+ throw new AuthSdkError({
613
+ code: mapHttpStatusToAuthErrorCode(response.status),
614
+ details: {
615
+ apiCode: parsed.code,
616
+ apiDetails: parsed.details
617
+ },
618
+ message: parsed.message ?? buildApiErrorMessage(options.method, options.path),
619
+ status: response.status
620
+ });
621
+ }
622
+ if (response.status === httpStatus.noContent) {
623
+ return toTypedValue(null);
624
+ }
625
+ const payload = await parseJsonPayload(response);
626
+ return toTypedValue(payload);
627
+ }
628
+ function getJson(path) {
629
+ return request({
630
+ method: "GET",
631
+ path
632
+ });
633
+ }
634
+ function postJson(path, body) {
635
+ return request({
636
+ body,
637
+ method: "POST",
638
+ path
639
+ });
640
+ }
641
+ function postEmpty(path) {
642
+ return request({
643
+ expectedStatus: httpStatus.noContent,
644
+ method: "POST",
645
+ path
646
+ });
647
+ }
648
+
649
+ // src/core/runtime.ts
650
+ var browserRuntimeErrorMessage = "Browser runtime is required for this auth operation.";
651
+ function isBrowserRuntime() {
652
+ return typeof window !== "undefined";
653
+ }
654
+ function assertBrowserRuntime() {
655
+ if (isBrowserRuntime()) {
656
+ return;
657
+ }
658
+ throw new AuthSdkError({
659
+ code: authErrorCode.apiError,
660
+ details: null,
661
+ message: browserRuntimeErrorMessage,
662
+ status: null
663
+ });
664
+ }
665
+
666
+ // src/core/return-to-storage.ts
667
+ function sanitizeReturnTo(value) {
668
+ if (!value) {
669
+ return authRoutes.root;
670
+ }
671
+ if (!value.startsWith(authRoutes.root)) {
672
+ return authRoutes.root;
673
+ }
674
+ const protocolRelativePrefix = "//";
675
+ if (value.startsWith(protocolRelativePrefix)) {
676
+ return authRoutes.root;
677
+ }
678
+ return value;
679
+ }
680
+ function getCurrentBrowserPath() {
681
+ return `${window.location.pathname}${window.location.search}${window.location.hash}`;
682
+ }
683
+ function saveReturnToPath(returnTo) {
684
+ assertBrowserRuntime();
685
+ const fallbackPath = getCurrentBrowserPath();
686
+ const sanitized = sanitizeReturnTo(returnTo ?? fallbackPath);
687
+ window.sessionStorage.setItem(authStorageKeys.returnTo, sanitized);
688
+ return sanitized;
689
+ }
690
+ function consumeReturnToPath() {
691
+ assertBrowserRuntime();
692
+ const value = window.sessionStorage.getItem(authStorageKeys.returnTo);
693
+ window.sessionStorage.removeItem(authStorageKeys.returnTo);
694
+ return sanitizeReturnTo(value);
695
+ }
696
+
697
+ // src/core/session-store.ts
698
+ var SessionStore = class {
699
+ listeners = /* @__PURE__ */ new Set();
700
+ resolvedSession = null;
701
+ resolveState = "pending";
702
+ getSessionIfResolved() {
703
+ if (this.resolveState === "pending") {
704
+ return null;
705
+ }
706
+ return this.resolvedSession;
707
+ }
708
+ hasResolvedSession() {
709
+ return this.resolveState === "resolved";
710
+ }
711
+ setSession(session) {
712
+ this.resolveState = "resolved";
713
+ this.resolvedSession = session;
714
+ for (const listener of this.listeners) {
715
+ listener(session);
716
+ }
717
+ }
718
+ subscribe(listener) {
719
+ this.listeners.add(listener);
720
+ return () => {
721
+ this.listeners.delete(listener);
722
+ };
723
+ }
724
+ };
725
+
726
+ // src/core/auth-client.ts
727
+ function createPendingRedirectPromise() {
728
+ return new Promise(() => {
729
+ });
730
+ }
731
+ function createInvalidSessionPayloadError(path) {
732
+ return new AuthSdkError({
733
+ code: authErrorCode.apiError,
734
+ details: null,
735
+ message: `Auth API response has invalid session shape: ${path}`,
736
+ status: null
737
+ });
738
+ }
739
+ function toValidatedSession(payload, path) {
740
+ if (!isGhostlySession(payload)) {
741
+ throw createInvalidSessionPayloadError(path);
742
+ }
743
+ return payload;
744
+ }
745
+ function toCallbackFailure(error) {
746
+ if (error instanceof AuthSdkError) {
747
+ if (error.status === httpStatus.unauthorized) {
748
+ return new AuthSdkError({
749
+ code: authErrorCode.callbackInvalidToken,
750
+ details: error.details,
751
+ message: "Callback JWT is invalid or expired.",
752
+ status: error.status
753
+ });
754
+ }
755
+ return new AuthSdkError({
756
+ code: authErrorCode.callbackValidationFailed,
757
+ details: error.details,
758
+ message: "Keycloak callback validation failed.",
759
+ status: error.status
760
+ });
761
+ }
762
+ return new AuthSdkError({
763
+ code: authErrorCode.callbackValidationFailed,
764
+ details: error,
765
+ message: "Keycloak callback validation failed.",
766
+ status: null
767
+ });
768
+ }
769
+ function createNoopBroadcastSync() {
770
+ return {
771
+ close() {
772
+ },
773
+ publishSession() {
774
+ }
775
+ };
776
+ }
777
+ function createSafeBroadcastSync(onSessionUpdated) {
778
+ try {
779
+ return createBroadcastSync({
780
+ onSessionUpdated
781
+ });
782
+ } catch (error) {
783
+ if (error instanceof AuthSdkError && error.code === authErrorCode.broadcastChannelUnsupported) {
784
+ return createNoopBroadcastSync();
785
+ }
786
+ throw error;
787
+ }
788
+ }
789
+ async function fetchCurrentSessionFromApi() {
790
+ const payload = await getJson(authEndpoints.session);
791
+ if (payload === null) {
792
+ return null;
793
+ }
794
+ return toValidatedSession(payload, authEndpoints.session);
795
+ }
796
+ function createAuthClient() {
797
+ assertBrowserRuntime();
798
+ const sessionStore = new SessionStore();
799
+ const broadcastSync = createSafeBroadcastSync((session) => {
800
+ sessionStore.setSession(session);
801
+ });
802
+ const getSession = async (options) => {
803
+ const forceRefresh = options?.forceRefresh ?? false;
804
+ if (sessionStore.hasResolvedSession() && !forceRefresh) {
805
+ return sessionStore.getSessionIfResolved();
806
+ }
807
+ const session = await fetchCurrentSessionFromApi();
808
+ sessionStore.setSession(session);
809
+ broadcastSync.publishSession(session);
810
+ return session;
811
+ };
812
+ const requireSession = async () => {
813
+ const session = await getSession();
814
+ if (session) {
815
+ return session;
816
+ }
817
+ throw new AuthSdkError({
818
+ code: authErrorCode.unauthorized,
819
+ details: null,
820
+ message: "Authenticated session is required.",
821
+ status: httpStatus.unauthorized
822
+ });
823
+ };
824
+ const login = (options) => {
825
+ saveReturnToPath(options?.returnTo);
826
+ window.location.assign(authEndpoints.loginStart);
827
+ };
828
+ const processCallback = async () => {
829
+ const currentUrl = new URL(window.location.href);
830
+ const token = readCallbackToken(currentUrl);
831
+ if (!token) {
832
+ throw new AuthSdkError({
833
+ code: authErrorCode.callbackMissingToken,
834
+ details: null,
835
+ message: "Missing callback token query parameter.",
836
+ status: httpStatus.badRequest
837
+ });
838
+ }
839
+ const cleanedUrl = removeCallbackToken(currentUrl);
840
+ replaceBrowserHistory(cleanedUrl);
841
+ try {
842
+ const payload = await postJson(
843
+ authEndpoints.validateKeycloakToken,
844
+ { token }
845
+ );
846
+ const session = toValidatedSession(payload.session, authEndpoints.validateKeycloakToken);
847
+ sessionStore.setSession(session);
848
+ broadcastSync.publishSession(session);
849
+ return {
850
+ redirectTo: consumeReturnToPath(),
851
+ session
852
+ };
853
+ } catch (error) {
854
+ throw toCallbackFailure(error);
855
+ }
856
+ };
857
+ const completeCallbackRedirect = async () => {
858
+ const result = await processCallback();
859
+ window.location.replace(result.redirectTo);
860
+ return createPendingRedirectPromise();
861
+ };
862
+ const logout = async () => {
863
+ await postEmpty(authEndpoints.logout);
864
+ sessionStore.setSession(null);
865
+ broadcastSync.publishSession(null);
866
+ };
867
+ const subscribe = sessionStore.subscribe.bind(sessionStore);
868
+ return {
869
+ completeCallbackRedirect,
870
+ getSession,
871
+ login,
872
+ logout,
873
+ processCallback,
874
+ requireSession,
875
+ subscribe
876
+ };
877
+ }
878
+
879
+ // src/adapters/next/client-guard.ts
880
+ function createPendingRedirectPromise2() {
881
+ return new Promise(() => {
882
+ });
883
+ }
884
+ async function ensureClientAuthenticated(client) {
885
+ const authClient = client ?? createAuthClient();
886
+ try {
887
+ return await authClient.requireSession();
888
+ } catch (error) {
889
+ if (error instanceof AuthSdkError && error.code === authErrorCode.unauthorized) {
890
+ authClient.login();
891
+ return createPendingRedirectPromise2();
892
+ }
893
+ throw error;
894
+ }
895
+ }
896
+
897
+ export { createNextAuthRouteHandlers, ensureClientAuthenticated, getNextServerSession, getServerSession, requireNextServerSession, requireServerSession, tryGetNextServerSession };
898
+ //# sourceMappingURL=next.js.map
899
+ //# sourceMappingURL=next.js.map