@civic/auth 0.0.1-beta.0 → 0.0.1-beta.1

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/nextjs.mjs CHANGED
@@ -1,4 +1,4 @@
1
- 'use client'
1
+ "use server";
2
2
  var __defProp = Object.defineProperty;
3
3
  var __defProps = Object.defineProperties;
4
4
  var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
@@ -39,61 +39,850 @@ var __async = (__this, __arguments, generator) => {
39
39
  });
40
40
  };
41
41
 
42
- // src/nextjs/index.ts
43
- import { NextResponse } from "next/server";
44
- import { validateJWT } from "oslo/jwt";
45
- var defaults = {
46
- loginUrl: "/"
47
- // by default, redirect to the root
48
- };
49
- var redirectNoLoops = (newUrl, req) => {
50
- const redirectUrl = new URL(newUrl, req.url);
51
- req.nextUrl.searchParams.forEach((value, key) => {
52
- redirectUrl.searchParams.append(key, value);
42
+ // src/lib/logger.ts
43
+ import debug from "debug";
44
+ var PACKAGE_NAME = "@civic/auth";
45
+ var DebugLogger = class {
46
+ constructor(namespace) {
47
+ this.debugLogger = debug(`${PACKAGE_NAME}:${namespace}:debug`);
48
+ this.infoLogger = debug(`${PACKAGE_NAME}:${namespace}:info`);
49
+ this.warnLogger = debug(`${PACKAGE_NAME}:${namespace}:warn`);
50
+ this.errorLogger = debug(`${PACKAGE_NAME}:${namespace}:error`);
51
+ this.debugLogger.color = "4";
52
+ this.infoLogger.color = "2";
53
+ this.warnLogger.color = "3";
54
+ this.errorLogger.color = "1";
55
+ }
56
+ debug(message, ...args) {
57
+ this.debugLogger(message, ...args);
58
+ }
59
+ info(message, ...args) {
60
+ this.infoLogger(message, ...args);
61
+ }
62
+ warn(message, ...args) {
63
+ this.warnLogger(message, ...args);
64
+ }
65
+ error(message, ...args) {
66
+ this.errorLogger(message, ...args);
67
+ }
68
+ };
69
+ var createLogger = (namespace) => new DebugLogger(namespace);
70
+ var loggers = {
71
+ // Next.js specific loggers
72
+ nextjs: {
73
+ routes: createLogger("api:routes"),
74
+ middleware: createLogger("api:middleware"),
75
+ handlers: {
76
+ auth: createLogger("api:handlers:auth")
77
+ }
78
+ },
79
+ // React specific loggers
80
+ react: {
81
+ components: createLogger("react:components"),
82
+ hooks: createLogger("react:hooks"),
83
+ context: createLogger("react:context")
84
+ },
85
+ // Shared utilities loggers
86
+ services: {
87
+ validation: createLogger("utils:validation"),
88
+ network: createLogger("utils:network")
89
+ }
90
+ };
91
+
92
+ // src/nextjs/config.ts
93
+ var logger = loggers.nextjs.handlers.auth;
94
+ var defaultAuthConfig = {
95
+ oauthServer: "https://auth-dev.civic.com/oauth",
96
+ callbackUrl: "/api/auth/callback",
97
+ challengeUrl: "/api/auth/challenge",
98
+ logoutUrl: "/api/auth/logout",
99
+ loginUrl: "/",
100
+ include: ["/*"],
101
+ exclude: [],
102
+ cookies: {
103
+ tokens: {
104
+ sameSite: "strict",
105
+ path: "/",
106
+ maxAge: 60 * 60
107
+ // 1 hour
108
+ },
109
+ user: {
110
+ sameSite: "strict",
111
+ path: "/",
112
+ maxAge: 60 * 60
113
+ // 1 hour
114
+ }
115
+ }
116
+ };
117
+ var withoutUndefined = (obj) => {
118
+ const result = {};
119
+ for (const key in obj) {
120
+ if (obj[key] !== void 0) {
121
+ result[key] = obj[key];
122
+ }
123
+ }
124
+ return result;
125
+ };
126
+ var resolveAuthConfig = (config = {}) => {
127
+ var _a, _b, _c, _d;
128
+ const configFromEnv = withoutUndefined({
129
+ clientId: process.env._civic_auth_client_id,
130
+ oauthServer: process.env._civic_oauth_server,
131
+ callbackUrl: process.env._civic_auth_callback_url,
132
+ loginUrl: process.env._civic_auth_login_url,
133
+ logoutUrl: process.env._civic_auth_logout_url,
134
+ include: (_a = process.env._civic_auth_includes) == null ? void 0 : _a.split(","),
135
+ exclude: (_b = process.env._civic_auth_excludes) == null ? void 0 : _b.split(","),
136
+ cookies: process.env._civic_auth_cookie_config ? JSON.parse(process.env._civic_auth_cookie_config) : void 0
137
+ });
138
+ const mergedConfig = __spreadProps(__spreadValues(__spreadValues(__spreadValues({}, defaultAuthConfig), configFromEnv), config), {
139
+ // Override with directly passed config
140
+ cookies: {
141
+ tokens: __spreadValues(__spreadValues({}, defaultAuthConfig.cookies.tokens), ((_c = config.cookies) == null ? void 0 : _c.tokens) || {}),
142
+ user: __spreadValues(__spreadValues({}, defaultAuthConfig.cookies.user), ((_d = config.cookies) == null ? void 0 : _d.user) || {})
143
+ }
53
144
  });
54
- if (redirectUrl.toString() !== req.url) {
55
- return NextResponse.redirect(redirectUrl);
145
+ logger.debug("Config from environment:", configFromEnv);
146
+ logger.debug("Resolved config:", mergedConfig);
147
+ if (mergedConfig.clientId === void 0) {
148
+ throw new Error("Civic Auth client ID is required");
56
149
  }
150
+ return mergedConfig;
57
151
  };
58
- var authMiddleware = (config = defaults) => (req) => __async(void 0, null, function* () {
59
- var _a;
60
- const idToken = (_a = req.cookies.get("id_token")) == null ? void 0 : _a.value;
61
- if (!idToken) {
62
- return redirectNoLoops(config.loginUrl, req);
152
+ var createCivicAuthPlugin = (clientId, authConfig = {}) => {
153
+ return (nextConfig) => {
154
+ const resolvedConfig = resolveAuthConfig(__spreadProps(__spreadValues({}, authConfig), { clientId }));
155
+ return __spreadProps(__spreadValues({}, nextConfig), {
156
+ env: __spreadProps(__spreadValues({}, nextConfig == null ? void 0 : nextConfig.env), {
157
+ // Internal environment variables - do not set these manually
158
+ _civic_auth_client_id: clientId,
159
+ _civic_oauth_server: resolvedConfig.oauthServer,
160
+ _civic_auth_callback_url: resolvedConfig.callbackUrl,
161
+ _civic_auth_login_url: resolvedConfig.loginUrl,
162
+ _civic_auth_logout_url: resolvedConfig.logoutUrl,
163
+ _civic_auth_includes: resolvedConfig.include.join(","),
164
+ _civic_auth_excludes: resolvedConfig.exclude.join(","),
165
+ _civic_auth_cookie_config: JSON.stringify(resolvedConfig.cookies)
166
+ })
167
+ });
168
+ };
169
+ };
170
+
171
+ // src/nextjs/routeHandler.ts
172
+ import { NextResponse } from "next/server.js";
173
+ import { revalidatePath } from "next/cache.js";
174
+
175
+ // src/nextjs/NextJSSessionService.ts
176
+ import { cookies } from "next/headers.js";
177
+
178
+ // src/services/UserInfoService.ts
179
+ import { parseJWT } from "oslo/jwt";
180
+ var UserInfoServiceImpl = class {
181
+ constructor(endpoints) {
182
+ this.endpoints = endpoints;
183
+ }
184
+ extractUserFromIdToken(idToken) {
185
+ const parsedJWT = parseJWT(idToken);
186
+ if (!parsedJWT) {
187
+ return null;
188
+ }
189
+ return parsedJWT.payload;
190
+ }
191
+ getUserInfo(accessToken, idToken) {
192
+ return __async(this, null, function* () {
193
+ if (idToken) {
194
+ return this.extractUserFromIdToken(idToken);
195
+ }
196
+ const userInfo = yield fetch(this.endpoints.userinfo, {
197
+ headers: { Authorization: `Bearer ${accessToken}` }
198
+ });
199
+ return userInfo.json();
200
+ });
201
+ }
202
+ };
203
+
204
+ // src/services/SessionService.ts
205
+ import { OAuth2Client, generateCodeVerifier } from "oslo/oauth2";
206
+ import * as jose from "jose";
207
+
208
+ // src/lib/oauth.ts
209
+ import { v4 as uuid } from "uuid";
210
+ var getIssuerVariations = (issuer) => {
211
+ const issuerWithoutSlash = issuer.endsWith("/") ? issuer.slice(0, issuer.length - 1) : issuer;
212
+ const issuerWithSlash = `${issuerWithoutSlash}/`;
213
+ return [issuerWithoutSlash, issuerWithSlash];
214
+ };
215
+ var addSlashIfNeeded = (url) => url.endsWith("/") ? url : `${url}/`;
216
+ var getOauthEndpoints = (oauthServer) => __async(void 0, null, function* () {
217
+ const openIdConfigResponse = yield fetch(
218
+ `${addSlashIfNeeded(oauthServer)}.well-known/openid-configuration`
219
+ );
220
+ const openIdConfig = yield openIdConfigResponse.json();
221
+ return {
222
+ jwks: openIdConfig.jwks_uri,
223
+ auth: openIdConfig.authorization_endpoint,
224
+ token: openIdConfig.token_endpoint,
225
+ userinfo: openIdConfig.userinfo_endpoint
226
+ };
227
+ });
228
+ var generateState = (displayMode) => {
229
+ const jsonString = JSON.stringify({
230
+ uuid: uuid(),
231
+ displayMode
232
+ });
233
+ return btoa(jsonString);
234
+ };
235
+ var displayModeFromState = (state, sessionDisplayMode) => {
236
+ try {
237
+ const jsonString = btoa(state);
238
+ return JSON.parse(jsonString).displayMode;
239
+ } catch (e) {
240
+ console.error("Failed to parse displayMode from state:", e);
241
+ return sessionDisplayMode;
242
+ }
243
+ };
244
+
245
+ // src/utils.ts
246
+ import { clsx } from "clsx";
247
+ import { twMerge } from "tailwind-merge";
248
+ var isPopupBlocked = () => {
249
+ const popup = window.open("", "", "width=1,height=1");
250
+ if (!popup) {
251
+ return true;
63
252
  }
64
253
  try {
65
- const jwt = yield validateJWT(
66
- "HS256",
67
- Buffer.from(process.env.LOGIN_JWT_SECRET),
68
- idToken
254
+ if (typeof popup.closed === "undefined") {
255
+ throw new Error("Popup is blocked");
256
+ }
257
+ } catch (e) {
258
+ return true;
259
+ }
260
+ popup.close();
261
+ return false;
262
+ };
263
+
264
+ // src/services/SessionService.ts
265
+ var AuthSessionServiceImpl = class {
266
+ constructor(clientId, redirectUrl, oauthServer, inputEndpoints) {
267
+ this.clientId = clientId;
268
+ this.redirectUrl = redirectUrl;
269
+ this.oauthServer = oauthServer;
270
+ this.inputEndpoints = inputEndpoints;
271
+ this.codeVerifier = void 0;
272
+ this.refreshTokenTimeout = null;
273
+ this.codeVerifier = this.getCodeVerifier();
274
+ this.endpoints = inputEndpoints;
275
+ }
276
+ getCodeVerifier() {
277
+ return generateCodeVerifier();
278
+ }
279
+ getUserInfoService() {
280
+ return __async(this, null, function* () {
281
+ if (this.userInfoService) {
282
+ return this.userInfoService;
283
+ }
284
+ const endpoints = yield this.getEndpoints();
285
+ this.userInfoService = new UserInfoServiceImpl(endpoints);
286
+ return this.userInfoService;
287
+ });
288
+ }
289
+ getEndpoints() {
290
+ return __async(this, null, function* () {
291
+ var _a;
292
+ if ((_a = this.endpoints) == null ? void 0 : _a.auth) {
293
+ return this.endpoints;
294
+ }
295
+ const jwksEndpoints = yield getOauthEndpoints(this.oauthServer);
296
+ return this.endpoints ? __spreadValues(__spreadValues({}, this.endpoints), jwksEndpoints) : jwksEndpoints;
297
+ });
298
+ }
299
+ getOauth2Client() {
300
+ return __async(this, null, function* () {
301
+ if (this.oauth2Client) {
302
+ return this.oauth2Client;
303
+ }
304
+ const endpoints = yield this.getEndpoints();
305
+ this.oauth2Client = new OAuth2Client(
306
+ this.clientId,
307
+ endpoints.auth,
308
+ endpoints.token,
309
+ // this
310
+ { redirectURI: this.redirectUrl }
311
+ );
312
+ return this.oauth2Client;
313
+ });
314
+ }
315
+ getSessionData() {
316
+ return JSON.parse(
317
+ localStorage.getItem(`civic-auth:${this.clientId}`) || "{}"
69
318
  );
70
- if (!jwt) {
71
- return NextResponse.json("Malformed token", { status: 400 });
319
+ }
320
+ updateSessionData(data) {
321
+ localStorage.setItem(
322
+ `civic-auth:${this.clientId}`,
323
+ JSON.stringify(__spreadValues({}, data))
324
+ );
325
+ }
326
+ getUser() {
327
+ return JSON.parse(
328
+ localStorage.getItem(`civic-auth:${this.clientId}:user`) || "{}"
329
+ );
330
+ }
331
+ setUser(data) {
332
+ localStorage.setItem(
333
+ `civic-auth:${this.clientId}:user`,
334
+ JSON.stringify(data === null ? {} : data)
335
+ );
336
+ }
337
+ clearSessionData() {
338
+ localStorage.setItem(`civic-auth:${this.clientId}`, JSON.stringify({}));
339
+ }
340
+ getAuthorizationUrlWithChallenge(state, scopes) {
341
+ return __async(this, null, function* () {
342
+ var _a;
343
+ const oauth2Client = yield this.getOauth2Client();
344
+ if ((_a = this.endpoints) == null ? void 0 : _a.challenge) {
345
+ const challenge = yield fetch(this.endpoints.challenge).then(
346
+ (res) => res.json().then((data) => data.challenge)
347
+ );
348
+ const oAuthUrl2 = yield oauth2Client.createAuthorizationURL({
349
+ state,
350
+ scopes
351
+ });
352
+ oAuthUrl2.searchParams.append("code_challenge", challenge);
353
+ oAuthUrl2.searchParams.append("code_challenge_method", "S256");
354
+ return oAuthUrl2;
355
+ }
356
+ const oAuthUrl = yield oauth2Client.createAuthorizationURL({
357
+ state,
358
+ codeVerifier: this.codeVerifier,
359
+ codeChallengeMethod: "S256",
360
+ scopes
361
+ });
362
+ return oAuthUrl;
363
+ });
364
+ }
365
+ getAuthorizationUrl(scopes, displayMode, nonce) {
366
+ return __async(this, null, function* () {
367
+ const state = generateState(displayMode);
368
+ const existingSessionData = this.getSessionData();
369
+ this.updateSessionData(__spreadProps(__spreadValues({}, existingSessionData), {
370
+ codeVerifier: this.codeVerifier,
371
+ displayMode
372
+ }));
373
+ const oAuthUrl = yield this.getAuthorizationUrlWithChallenge(state, scopes);
374
+ if (nonce) {
375
+ oAuthUrl.searchParams.append("nonce", nonce);
376
+ }
377
+ oAuthUrl.searchParams.append("prompt", "consent");
378
+ return oAuthUrl.toString();
379
+ });
380
+ }
381
+ // TODO fix the Window reference
382
+ loadAuthorizationUrl(authorizationURL, displayMode) {
383
+ switch (displayMode) {
384
+ case "iframe":
385
+ break;
386
+ case "redirect":
387
+ window.location.href = authorizationURL;
388
+ break;
389
+ case "new_tab":
390
+ window.open(authorizationURL, "_blank");
391
+ break;
392
+ case "custom_tab":
393
+ break;
72
394
  }
73
- if (!jwt.expiresAt) {
74
- return NextResponse.json("Token missing expiration", { status: 400 });
395
+ }
396
+ init() {
397
+ return __async(this, null, function* () {
398
+ this.updateSessionData({ authenticated: false });
399
+ });
400
+ }
401
+ determineDisplayMode(displayMode) {
402
+ if (isPopupBlocked() && displayMode === "iframe") {
403
+ displayMode = "redirect";
404
+ }
405
+ return displayMode;
406
+ }
407
+ signIn(displayMode, scopes, nonce) {
408
+ return __async(this, null, function* () {
409
+ const authorizationURL = yield this.getAuthorizationUrl(
410
+ scopes,
411
+ displayMode,
412
+ nonce
413
+ );
414
+ this.loadAuthorizationUrl(authorizationURL, displayMode);
415
+ });
416
+ }
417
+ tokenExchange(responseUrl) {
418
+ return __async(this, null, function* () {
419
+ let session = this.getSessionData();
420
+ if (!session.authenticated) {
421
+ const url = new URL(responseUrl);
422
+ const authorizationCode = url.searchParams.get("code");
423
+ const returnedState = url.searchParams.get("state");
424
+ if (!authorizationCode || !returnedState) {
425
+ throw new Error("Invalid authorization response");
426
+ }
427
+ const codeVerifier = session.codeVerifier;
428
+ const oauth2Client = yield this.getOauth2Client();
429
+ const tokens = yield oauth2Client.validateAuthorizationCode(
430
+ authorizationCode,
431
+ {
432
+ codeVerifier
433
+ }
434
+ );
435
+ try {
436
+ yield this.validateTokens(tokens);
437
+ } catch (error) {
438
+ console.error("tokenExchange tokens", { error, tokens });
439
+ throw new Error(
440
+ `OIDC tokens validation failed: ${error.message}`
441
+ );
442
+ }
443
+ const parsedDisplayMode = displayModeFromState(
444
+ returnedState,
445
+ session.displayMode
446
+ );
447
+ session = __spreadProps(__spreadValues({}, session), {
448
+ displayMode: parsedDisplayMode,
449
+ idToken: tokens.id_token,
450
+ authenticated: true,
451
+ state: returnedState,
452
+ accessToken: tokens.access_token,
453
+ refreshToken: tokens.refresh_token,
454
+ timestamp: Date.now(),
455
+ expiresIn: tokens.expires_in
456
+ });
457
+ this.updateSessionData(session);
458
+ const user = yield (yield this.getUserInfoService()).getUserInfo(tokens.access_token, tokens.id_token || null);
459
+ this.setUser(user);
460
+ }
461
+ this.setupTokenRefresh(session);
462
+ if (session.displayMode === "new_tab") {
463
+ window.close();
464
+ } else if (session.displayMode === "redirect") {
465
+ }
466
+ return session;
467
+ });
468
+ }
469
+ setupTokenRefresh(session) {
470
+ if (this.refreshTokenTimeout) {
471
+ clearTimeout(this.refreshTokenTimeout);
75
472
  }
76
- if (jwt.expiresAt.getTime() < Date.now()) {
77
- return redirectNoLoops(config.loginUrl, req);
473
+ if (session.expiresIn) {
474
+ const elapsedTime = Date.now() - (session.timestamp || 0);
475
+ const remainingTime = session.expiresIn * 1e3 - elapsedTime;
476
+ const refreshTime = Math.max(0, remainingTime - 6e4);
477
+ this.refreshTokenTimeout = setTimeout(() => {
478
+ this.refreshToken().then((newSession) => {
479
+ console.log("Token refreshed successfully", newSession);
480
+ }).catch((error) => {
481
+ console.error("Failed to refresh token:", error);
482
+ this.updateSessionData({});
483
+ });
484
+ }, refreshTime);
78
485
  }
79
- const token = jwt.payload;
80
- if (!token.id) {
81
- return NextResponse.json("Unauthorized", { status: 401 });
486
+ }
487
+ refreshToken() {
488
+ return __async(this, null, function* () {
489
+ const sessionData = this.getSessionData();
490
+ if (!sessionData.refreshToken) {
491
+ throw new Error("No refresh token available");
492
+ }
493
+ const oauth2Client = yield this.getOauth2Client();
494
+ const tokens = yield oauth2Client.refreshAccessToken(
495
+ sessionData.refreshToken
496
+ );
497
+ const session = __spreadProps(__spreadValues({}, sessionData), {
498
+ idToken: tokens.id_token,
499
+ authenticated: true,
500
+ accessToken: tokens.access_token,
501
+ refreshToken: tokens.refresh_token,
502
+ timestamp: Date.now(),
503
+ expiresIn: tokens.expires_in
504
+ });
505
+ this.updateSessionData(session);
506
+ this.setupTokenRefresh(session);
507
+ return session;
508
+ });
509
+ }
510
+ getUserInfo() {
511
+ return __async(this, null, function* () {
512
+ const sessionData = this.getSessionData();
513
+ if (!sessionData.accessToken) {
514
+ throw new Error("No access token available");
515
+ }
516
+ const userInfoService = yield this.getUserInfoService();
517
+ return userInfoService.getUserInfo(
518
+ sessionData.accessToken,
519
+ sessionData.idToken || null
520
+ );
521
+ });
522
+ }
523
+ /**
524
+ * Uses the jose library to validate a JWT token using the OAuth JWKS endpoint
525
+ * @param {string} token
526
+ * @returns {Promise<jose.JWTPayload>}
527
+ * @throws {Error} if the token is invalid
528
+ */
529
+ validateTokens(tokens) {
530
+ return __async(this, null, function* () {
531
+ const endpoints = yield this.getEndpoints();
532
+ const JWKS = jose.createRemoteJWKSet(new URL(endpoints.jwks));
533
+ const returnPayload = {};
534
+ console.log("issuer", getIssuerVariations(this.oauthServer));
535
+ const idTokenResponse = yield jose.jwtVerify(tokens.id_token, JWKS, {
536
+ issuer: getIssuerVariations(this.oauthServer),
537
+ audience: this.clientId
538
+ });
539
+ returnPayload.idToken = idTokenResponse.payload;
540
+ const accessTokenResponse = yield jose.jwtVerify(
541
+ tokens.access_token,
542
+ JWKS,
543
+ {
544
+ issuer: getIssuerVariations(this.oauthServer)
545
+ }
546
+ );
547
+ returnPayload.accessToken = accessTokenResponse.payload;
548
+ if (tokens.refresh_token) {
549
+ returnPayload.refreshToken = tokens.refresh_token;
550
+ }
551
+ return returnPayload;
552
+ });
553
+ }
554
+ validateExistingSession() {
555
+ return __async(this, null, function* () {
556
+ const sessionData = this.getSessionData();
557
+ try {
558
+ if (!sessionData.idToken || !sessionData.accessToken) {
559
+ const unAuthenticatedSession = __spreadProps(__spreadValues({}, sessionData), { authenticated: false });
560
+ this.updateSessionData(unAuthenticatedSession);
561
+ return unAuthenticatedSession;
562
+ }
563
+ yield this.validateTokens({
564
+ id_token: sessionData.idToken,
565
+ access_token: sessionData.accessToken,
566
+ refresh_token: sessionData.refreshToken
567
+ });
568
+ sessionData.authenticated = true;
569
+ return sessionData;
570
+ } catch (error) {
571
+ console.warn("Failed to validate existing tokens", error);
572
+ const unAuthenticatedSession = {
573
+ authenticated: false
574
+ };
575
+ this.updateSessionData(unAuthenticatedSession);
576
+ return unAuthenticatedSession;
577
+ }
578
+ });
579
+ }
580
+ };
581
+
582
+ // src/nextjs/cookies.ts
583
+ var createSecureTokenCookies = (response, sessionData, config) => {
584
+ var _a, _b;
585
+ const maxAge = (_a = sessionData.expiresIn) != null ? _a : 3600;
586
+ const cookieOptions = __spreadProps(__spreadValues({}, (_b = config.cookies) == null ? void 0 : _b.tokens), {
587
+ maxAge
588
+ });
589
+ if (sessionData.accessToken) {
590
+ response.cookies.set("access_token", sessionData.accessToken, __spreadProps(__spreadValues({}, cookieOptions), {
591
+ httpOnly: true
592
+ }));
593
+ }
594
+ if (sessionData.idToken) {
595
+ response.cookies.set("id_token", sessionData.idToken, __spreadProps(__spreadValues({}, cookieOptions), {
596
+ httpOnly: true
597
+ }));
598
+ }
599
+ if (sessionData.refreshToken) {
600
+ response.cookies.set("refresh_token", sessionData.refreshToken, __spreadProps(__spreadValues({}, cookieOptions), {
601
+ httpOnly: true
602
+ }));
603
+ }
604
+ };
605
+ var createUserInfoCookie = (response, user, sessionData, config) => {
606
+ var _a, _b, _c;
607
+ if (!user) {
608
+ response.cookies.set("user", "", __spreadProps(__spreadValues({}, (_a = config.cookies) == null ? void 0 : _a.user), {
609
+ maxAge: 0
610
+ }));
611
+ return;
612
+ }
613
+ const maxAge = (_b = sessionData.expiresIn) != null ? _b : 3600;
614
+ const frontendUser = __spreadValues({}, user);
615
+ response.cookies.set("user", JSON.stringify(frontendUser), __spreadProps(__spreadValues({}, (_c = config.cookies) == null ? void 0 : _c.user), {
616
+ maxAge
617
+ }));
618
+ };
619
+ var clearAuthCookies = (response, config) => {
620
+ var _a, _b;
621
+ const clearOptions = __spreadProps(__spreadValues({}, (_a = config.cookies) == null ? void 0 : _a.tokens), {
622
+ maxAge: 0
623
+ });
624
+ response.cookies.set("access_token", "", clearOptions);
625
+ response.cookies.set("id_token", "", clearOptions);
626
+ response.cookies.set("refresh_token", "", clearOptions);
627
+ response.cookies.set("codeVerifier", "", clearOptions);
628
+ response.cookies.set("user", "", __spreadProps(__spreadValues({}, (_b = config.cookies) == null ? void 0 : _b.user), {
629
+ maxAge: 0
630
+ }));
631
+ };
632
+
633
+ // src/nextjs/NextJSSessionService.ts
634
+ var NextJSAuthSessionServiceImpl = class extends AuthSessionServiceImpl {
635
+ constructor(authConfig, request, response, inputEndpoints) {
636
+ super(
637
+ authConfig.clientId,
638
+ authConfig.callbackUrl,
639
+ authConfig.oauthServer,
640
+ inputEndpoints
641
+ );
642
+ this.authConfig = authConfig;
643
+ this.request = request;
644
+ this.response = response;
645
+ this.inputEndpoints = inputEndpoints;
646
+ }
647
+ getCodeVerifier() {
648
+ const codeVerifier = cookies().get("codeVerifier");
649
+ if (!codeVerifier) {
650
+ throw new Error("Code verifier not found in cookies");
82
651
  }
83
- const requestHeaders = new Headers(req.headers);
84
- requestHeaders.set("x-user-id", token.id);
85
- return NextResponse.next({
86
- request: __spreadProps(__spreadValues({}, req), {
87
- // New request headers
88
- headers: requestHeaders
89
- })
652
+ return codeVerifier.value;
653
+ }
654
+ getSessionData() {
655
+ var _a, _b, _c, _d;
656
+ const authenticated = cookies().get("access_token") !== void 0;
657
+ return {
658
+ authenticated,
659
+ codeVerifier: (_a = cookies().get("codeVerifier")) == null ? void 0 : _a.value,
660
+ accessToken: (_b = cookies().get("access_token")) == null ? void 0 : _b.value,
661
+ idToken: (_c = cookies().get("id_token")) == null ? void 0 : _c.value,
662
+ refreshToken: (_d = cookies().get("refresh_token")) == null ? void 0 : _d.value
663
+ };
664
+ }
665
+ updateSessionData(data) {
666
+ createSecureTokenCookies(
667
+ this.response,
668
+ data,
669
+ this.authConfig
670
+ );
671
+ }
672
+ getUser() {
673
+ const userCookie = cookies().get("user");
674
+ if (!userCookie) return null;
675
+ return JSON.parse(userCookie.value);
676
+ }
677
+ setUser(user) {
678
+ createUserInfoCookie(
679
+ this.response,
680
+ user,
681
+ { authenticated: true },
682
+ this.authConfig
683
+ );
684
+ }
685
+ clearSessionData() {
686
+ clearAuthCookies(this.response, this.authConfig);
687
+ }
688
+ // TODO fix the Window reference
689
+ loadAuthorizationUrl() {
690
+ throw new Error("Not implemented");
691
+ }
692
+ init() {
693
+ return __async(this, null, function* () {
694
+ this.updateSessionData({ authenticated: false });
90
695
  });
91
- } catch (error) {
92
- console.error("Failed to decode id_token:", error);
93
- return redirectNoLoops(config.loginUrl, req);
94
696
  }
697
+ };
698
+
699
+ // src/nextjs/routeHandler.ts
700
+ import { generateCodeVerifier as generateCodeVerifier2 } from "oslo/oauth2";
701
+ var logger2 = loggers.nextjs.handlers.auth;
702
+ var AuthError = class extends Error {
703
+ constructor(message, status = 401) {
704
+ super(message);
705
+ this.status = status;
706
+ this.name = "AuthError";
707
+ }
708
+ };
709
+ function generateCodeChallenge(codeVerifier) {
710
+ return __async(this, null, function* () {
711
+ const encoder = new TextEncoder();
712
+ const data = encoder.encode(codeVerifier);
713
+ const digest = yield crypto.subtle.digest("SHA-256", data);
714
+ return btoa(String.fromCharCode(...new Uint8Array(digest))).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
715
+ });
716
+ }
717
+ function handleChallenge() {
718
+ return __async(this, null, function* () {
719
+ const codeVerifier = generateCodeVerifier2();
720
+ console.log("handleChallenge codeVerifier", codeVerifier);
721
+ const challenge = yield generateCodeChallenge(codeVerifier);
722
+ const response = NextResponse.json({ status: "success", challenge });
723
+ response.cookies.set("codeVerifier", codeVerifier, {
724
+ httpOnly: true,
725
+ secure: true,
726
+ sameSite: "strict"
727
+ });
728
+ return response;
729
+ });
730
+ }
731
+ function handleCallback(request, config) {
732
+ return __async(this, null, function* () {
733
+ const code = request.nextUrl.searchParams.get("code");
734
+ if (!code) {
735
+ throw new AuthError("Missing authorization code");
736
+ }
737
+ try {
738
+ const response = new NextResponse(`<html></html>`);
739
+ response.headers.set("Content-Type", "text/html; charset=utf-8");
740
+ const resolvedConfigs = resolveAuthConfig(config);
741
+ const callbackUrl = new URL(
742
+ resolvedConfigs == null ? void 0 : resolvedConfigs.callbackUrl,
743
+ request.url
744
+ ).toString();
745
+ const authService = getDefaultAuthSessionService(
746
+ __spreadProps(__spreadValues({}, resolvedConfigs), {
747
+ callbackUrl
748
+ }),
749
+ request,
750
+ response
751
+ );
752
+ console.log("handleCallback authService", authService);
753
+ const tokens = yield authService.tokenExchange(request.nextUrl.toString());
754
+ if (!tokens.accessToken) {
755
+ throw new AuthError("Missing access token");
756
+ }
757
+ return response;
758
+ } catch (error) {
759
+ logger2.error("Token exchange failed:", error);
760
+ throw new AuthError("Failed to authenticate user", 401);
761
+ }
762
+ });
763
+ }
764
+ function handleLogout(request, config) {
765
+ return __async(this, null, function* () {
766
+ var _a;
767
+ const resolvedConfigs = resolveAuthConfig(config);
768
+ const path = (_a = resolvedConfigs.loginUrl) != null ? _a : "/";
769
+ const redirectTarget = new URL(path, request.url).toString();
770
+ const response = NextResponse.redirect(redirectTarget);
771
+ clearAuthCookies(response, resolvedConfigs);
772
+ try {
773
+ revalidatePath(path);
774
+ } catch (error) {
775
+ logger2.warn("Failed to revalidate path after logout:", error);
776
+ }
777
+ return response;
778
+ });
779
+ }
780
+ var getDefaultAuthSessionService = (authConfig, request, response) => {
781
+ return new NextJSAuthSessionServiceImpl(authConfig, request, response);
782
+ };
783
+ function handler(authConfig = {}) {
784
+ return (request) => __async(this, null, function* () {
785
+ const config = resolveAuthConfig(authConfig);
786
+ try {
787
+ const pathname = request.nextUrl.pathname;
788
+ const pathSegments = pathname.split("/");
789
+ const lastSegment = pathSegments[pathSegments.length - 1];
790
+ switch (lastSegment) {
791
+ case "challenge":
792
+ return yield handleChallenge();
793
+ case "callback":
794
+ return yield handleCallback(request, config);
795
+ case "logout":
796
+ return yield handleLogout(request, config);
797
+ default:
798
+ throw new AuthError(`Invalid auth route: ${pathname}`, 404);
799
+ }
800
+ } catch (error) {
801
+ logger2.error("Auth handler error:", error);
802
+ const status = error instanceof AuthError ? error.status : 500;
803
+ const message = error instanceof Error ? error.message : "Authentication failed";
804
+ const response = NextResponse.json({ error: message }, { status });
805
+ clearAuthCookies(response, config);
806
+ return response;
807
+ }
808
+ });
809
+ }
810
+
811
+ // src/nextjs/GetUser.ts
812
+ import { cookies as cookies2 } from "next/headers.js";
813
+ var getUser = () => {
814
+ var _a;
815
+ const user = (_a = cookies2().get("user")) == null ? void 0 : _a.value;
816
+ if (!user) return null;
817
+ return JSON.parse(user);
818
+ };
819
+
820
+ // src/nextjs/middleware.ts
821
+ import { NextResponse as NextResponse2 } from "next/server.js";
822
+ import picomatch from "picomatch";
823
+ var matchGlob = (pathname, globPattern) => {
824
+ const matches = picomatch(globPattern);
825
+ return matches(pathname);
826
+ };
827
+ var matchesGlobs = (pathname, patterns) => patterns.some((pattern) => {
828
+ if (!pattern) return false;
829
+ console.log("matching", {
830
+ pattern,
831
+ pathname,
832
+ match: matchGlob(pathname, pattern)
833
+ });
834
+ return matchGlob(pathname, pattern);
835
+ });
836
+ var applyAuth = (authConfig, request) => __async(void 0, null, function* () {
837
+ const authConfigWithDefaults = resolveAuthConfig(authConfig);
838
+ const isAuthenticated = !!request.cookies.get("id_token");
839
+ if (request.nextUrl.pathname === authConfigWithDefaults.loginUrl) {
840
+ console.log("\u2192 Skipping auth check - this is the login URL");
841
+ return void 0;
842
+ }
843
+ if (!matchesGlobs(request.nextUrl.pathname, authConfigWithDefaults.include)) {
844
+ console.log("\u2192 Skipping auth check - path not in include patterns");
845
+ return void 0;
846
+ }
847
+ if (matchesGlobs(request.nextUrl.pathname, authConfigWithDefaults.exclude)) {
848
+ console.log("\u2192 Skipping auth check - path in exclude patterns");
849
+ return void 0;
850
+ }
851
+ if (!isAuthenticated) {
852
+ console.log("\u2192 No valid token found - redirecting to login");
853
+ const loginUrl = new URL(authConfigWithDefaults.loginUrl, request.url);
854
+ return NextResponse2.redirect(loginUrl);
855
+ }
856
+ console.log("\u2192 Auth check passed");
857
+ return void 0;
858
+ });
859
+ var authMiddleware = (authConfig = defaultAuthConfig) => (request) => __async(void 0, null, function* () {
860
+ const response = yield applyAuth(authConfig, request);
861
+ if (response) return response;
862
+ return NextResponse2.next();
95
863
  });
864
+ function withAuth(middleware) {
865
+ return (request) => __async(this, null, function* () {
866
+ const response = yield applyAuth({}, request);
867
+ if (response) return response;
868
+ return middleware(request);
869
+ });
870
+ }
871
+ function auth(authConfig = {}) {
872
+ return (middleware) => {
873
+ return (request) => __async(this, null, function* () {
874
+ const response = yield applyAuth(authConfig, request);
875
+ if (response) return response;
876
+ return middleware(request);
877
+ });
878
+ };
879
+ }
96
880
  export {
97
- authMiddleware
881
+ auth,
882
+ authMiddleware,
883
+ createCivicAuthPlugin,
884
+ getUser,
885
+ handler,
886
+ withAuth
98
887
  };
99
888
  //# sourceMappingURL=nextjs.mjs.map