@scalemule/nextjs 0.1.19 → 0.1.21

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.
@@ -1945,7 +1945,7 @@ var require_server = __commonJS({
1945
1945
  var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
1946
1946
  var server_exports = {};
1947
1947
  __export(server_exports, {
1948
- FlagClient: () => FlagClient2,
1948
+ FlagClient: () => FlagClient,
1949
1949
  allConditionsMatch: () => allConditionsMatch,
1950
1950
  evaluateFlag: () => evaluateFlag,
1951
1951
  hashToBucket: () => hashToBucket
@@ -2197,7 +2197,7 @@ var require_server = __commonJS({
2197
2197
  bucket: null
2198
2198
  };
2199
2199
  }
2200
- var FlagClient2 = class {
2200
+ var FlagClient = class {
2201
2201
  constructor(options) {
2202
2202
  this.config = null;
2203
2203
  this.flagIndex = /* @__PURE__ */ new Map();
@@ -5197,9 +5197,6 @@ function createWebhookHandler(config = {}) {
5197
5197
  }
5198
5198
  };
5199
5199
  }
5200
-
5201
- // src/server/bootstrap-flags.ts
5202
- var import_server2 = __toESM(require_server());
5203
5200
  var _clients = /* @__PURE__ */ new Map();
5204
5201
  var _initPromises = /* @__PURE__ */ new Map();
5205
5202
  var _serverClient = null;
@@ -5227,7 +5224,8 @@ async function getFlagClient(environment) {
5227
5224
  const pending = _initPromises.get(key);
5228
5225
  if (pending) return pending;
5229
5226
  const promise = (async () => {
5230
- const client = new import_server2.FlagClient({ apiKey, environment, gatewayUrl });
5227
+ const { FlagClient } = await Promise.resolve().then(() => __toESM(require_server()));
5228
+ const client = new FlagClient({ apiKey, environment, gatewayUrl });
5231
5229
  await Promise.race([
5232
5230
  client.init(),
5233
5231
  new Promise(
@@ -1943,7 +1943,7 @@ var require_server = __commonJS({
1943
1943
  var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
1944
1944
  var server_exports = {};
1945
1945
  __export(server_exports, {
1946
- FlagClient: () => FlagClient2,
1946
+ FlagClient: () => FlagClient,
1947
1947
  allConditionsMatch: () => allConditionsMatch,
1948
1948
  evaluateFlag: () => evaluateFlag,
1949
1949
  hashToBucket: () => hashToBucket
@@ -2195,7 +2195,7 @@ var require_server = __commonJS({
2195
2195
  bucket: null
2196
2196
  };
2197
2197
  }
2198
- var FlagClient2 = class {
2198
+ var FlagClient = class {
2199
2199
  constructor(options) {
2200
2200
  this.config = null;
2201
2201
  this.flagIndex = /* @__PURE__ */ new Map();
@@ -5195,9 +5195,6 @@ function createWebhookHandler(config = {}) {
5195
5195
  }
5196
5196
  };
5197
5197
  }
5198
-
5199
- // src/server/bootstrap-flags.ts
5200
- var import_server2 = __toESM(require_server());
5201
5198
  var _clients = /* @__PURE__ */ new Map();
5202
5199
  var _initPromises = /* @__PURE__ */ new Map();
5203
5200
  var _serverClient = null;
@@ -5225,7 +5222,8 @@ async function getFlagClient(environment) {
5225
5222
  const pending = _initPromises.get(key);
5226
5223
  if (pending) return pending;
5227
5224
  const promise = (async () => {
5228
- const client = new import_server2.FlagClient({ apiKey, environment, gatewayUrl });
5225
+ const { FlagClient } = await Promise.resolve().then(() => __toESM(require_server()));
5226
+ const client = new FlagClient({ apiKey, environment, gatewayUrl });
5229
5227
  await Promise.race([
5230
5228
  client.init(),
5231
5229
  new Promise(
@@ -0,0 +1 @@
1
+ export { L as LedveryRoutesConfig, a as LedverySessionData, f as createLedveryRoutes, g as getLedverySession } from '../ledvery-CxPzZpxP.mjs';
@@ -0,0 +1 @@
1
+ export { L as LedveryRoutesConfig, a as LedverySessionData, f as createLedveryRoutes, g as getLedverySession } from '../ledvery-CxPzZpxP.js';
@@ -0,0 +1,340 @@
1
+ 'use strict';
2
+
3
+ var ledvery = require('@scalemule/ledvery');
4
+
5
+ // src/server/ledvery.ts
6
+
7
+ // src/server/timing.ts
8
+ function constantTimeEqual(a, b) {
9
+ const maxLength = Math.max(a.length, b.length);
10
+ let mismatch = a.length ^ b.length;
11
+ for (let i = 0; i < maxLength; i++) {
12
+ const aCode = i < a.length ? a.charCodeAt(i) : 0;
13
+ const bCode = i < b.length ? b.charCodeAt(i) : 0;
14
+ mismatch |= aCode ^ bCode;
15
+ }
16
+ return mismatch === 0;
17
+ }
18
+
19
+ // src/server/ledvery-cookies.ts
20
+ var SM_LEDVERY_STATE_COOKIE = "sm_ledvery_state";
21
+ var SM_LEDVERY_PKCE_VERIFIER_COOKIE = "sm_ledvery_pkce_verifier";
22
+ var SM_LEDVERY_NONCE_COOKIE = "sm_ledvery_nonce";
23
+ var SM_LEDVERY_ID_TOKEN_COOKIE = "sm_ledvery_id_token";
24
+ var SM_LEDVERY_ACCESS_TOKEN_COOKIE = "sm_ledvery_access_token";
25
+ var FLOW_COOKIE_MAX_AGE = 60 * 10;
26
+ var SESSION_COOKIE_MAX_AGE = 60 * 60;
27
+ function flowCookieOptions() {
28
+ return {
29
+ httpOnly: true,
30
+ secure: process.env.NODE_ENV === "production",
31
+ sameSite: "lax",
32
+ path: "/",
33
+ maxAge: FLOW_COOKIE_MAX_AGE
34
+ };
35
+ }
36
+ function sessionCookieOptions(maxAge) {
37
+ return {
38
+ httpOnly: true,
39
+ secure: process.env.NODE_ENV === "production",
40
+ sameSite: "lax",
41
+ path: "/",
42
+ maxAge: maxAge ? Math.min(maxAge, SESSION_COOKIE_MAX_AGE) : SESSION_COOKIE_MAX_AGE
43
+ };
44
+ }
45
+ function setLedveryFlowCookies(response, params) {
46
+ const opts = flowCookieOptions();
47
+ setCookie(response, SM_LEDVERY_STATE_COOKIE, params.state, opts);
48
+ setCookie(response, SM_LEDVERY_PKCE_VERIFIER_COOKIE, params.codeVerifier, opts);
49
+ setCookie(response, SM_LEDVERY_NONCE_COOKIE, params.nonce, opts);
50
+ }
51
+ function validateAndConsumeLedveryFlowCookies(request, callbackState) {
52
+ const cookieState = getCookie(request, SM_LEDVERY_STATE_COOKIE);
53
+ const codeVerifier = getCookie(request, SM_LEDVERY_PKCE_VERIFIER_COOKIE);
54
+ const nonce = getCookie(request, SM_LEDVERY_NONCE_COOKIE);
55
+ if (!cookieState) return "Missing Ledvery state cookie - session may have expired";
56
+ if (!callbackState) return "Missing state parameter in callback";
57
+ if (!constantTimeEqual(cookieState, callbackState)) return "Ledvery state mismatch - possible CSRF attack";
58
+ if (!codeVerifier) return "Missing PKCE verifier cookie";
59
+ if (!nonce) return "Missing nonce cookie";
60
+ return { codeVerifier, nonce };
61
+ }
62
+ function setLedverySession(response, session, opts) {
63
+ const cookieOpts = sessionCookieOptions(opts?.cookies?.maxAge);
64
+ if (opts?.cookies?.domain) cookieOpts.domain = opts.cookies.domain;
65
+ if (opts?.cookies?.path) cookieOpts.path = opts.cookies.path;
66
+ if (opts?.cookies?.sameSite) cookieOpts.sameSite = opts.cookies.sameSite;
67
+ if (opts?.cookies?.secure !== void 0) cookieOpts.secure = opts.cookies.secure;
68
+ const sessionPayload = JSON.stringify({
69
+ idToken: session.idToken,
70
+ claims: session.claims,
71
+ expiresAt: session.expiresAt
72
+ });
73
+ setCookie(response, SM_LEDVERY_ID_TOKEN_COOKIE, sessionPayload, cookieOpts);
74
+ if (opts?.storeAccessToken && session.accessToken) {
75
+ setCookie(response, SM_LEDVERY_ACCESS_TOKEN_COOKIE, session.accessToken, cookieOpts);
76
+ }
77
+ }
78
+ function getLedverySession(request) {
79
+ const raw = getCookie(request, SM_LEDVERY_ID_TOKEN_COOKIE);
80
+ if (!raw) return null;
81
+ try {
82
+ const parsed = JSON.parse(raw);
83
+ const accessToken = getCookie(request, SM_LEDVERY_ACCESS_TOKEN_COOKIE);
84
+ return {
85
+ idToken: parsed.idToken || "",
86
+ accessToken,
87
+ claims: parsed.claims,
88
+ expiresAt: parsed.expiresAt
89
+ };
90
+ } catch {
91
+ return null;
92
+ }
93
+ }
94
+ function clearLedverySession(response) {
95
+ deleteCookie(response, SM_LEDVERY_ID_TOKEN_COOKIE);
96
+ deleteCookie(response, SM_LEDVERY_ACCESS_TOKEN_COOKIE);
97
+ }
98
+ function clearLedveryFlowCookies(response) {
99
+ deleteCookie(response, SM_LEDVERY_STATE_COOKIE);
100
+ deleteCookie(response, SM_LEDVERY_PKCE_VERIFIER_COOKIE);
101
+ deleteCookie(response, SM_LEDVERY_NONCE_COOKIE);
102
+ }
103
+ function getCookie(request, name) {
104
+ const header = request.headers.get("cookie") || "";
105
+ const match = header.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
106
+ return match ? decodeURIComponent(match[1]) : void 0;
107
+ }
108
+ function setCookie(response, name, value, opts) {
109
+ const parts = [
110
+ `${name}=${encodeURIComponent(value)}`,
111
+ `Path=${opts.path}`,
112
+ `Max-Age=${opts.maxAge}`,
113
+ `SameSite=${opts.sameSite.charAt(0).toUpperCase() + opts.sameSite.slice(1)}`
114
+ ];
115
+ if (opts.httpOnly) parts.push("HttpOnly");
116
+ if (opts.secure) parts.push("Secure");
117
+ if (opts.domain) parts.push(`Domain=${opts.domain}`);
118
+ response.headers.append("Set-Cookie", parts.join("; "));
119
+ }
120
+ function deleteCookie(response, name) {
121
+ response.headers.append("Set-Cookie", `${name}=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax`);
122
+ }
123
+
124
+ // src/server/redirect.ts
125
+ function normalizeOrigin(origin) {
126
+ try {
127
+ const u = new URL(origin);
128
+ return `${u.protocol}//${u.host}`.toLowerCase();
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
133
+ function isSafeSchemeRelative(input) {
134
+ return !(input.startsWith("//") || input.startsWith("\\"));
135
+ }
136
+ function validateSafeRedirect(input, opts = {}) {
137
+ const defaultPath = opts.defaultPath ?? "/";
138
+ if (!input || typeof input !== "string") return defaultPath;
139
+ const trimmed = input.trim();
140
+ if (!trimmed) return defaultPath;
141
+ if (!isSafeSchemeRelative(trimmed)) return defaultPath;
142
+ const colon = trimmed.indexOf(":");
143
+ if (colon > 0 && colon < 15) {
144
+ const scheme = trimmed.slice(0, colon).toLowerCase();
145
+ if (/^[a-z][a-z0-9+.-]*$/.test(scheme) && scheme !== "http" && scheme !== "https") {
146
+ return defaultPath;
147
+ }
148
+ }
149
+ if (trimmed.startsWith("/")) {
150
+ if (trimmed.length > 1 && trimmed[1] === "\\") return defaultPath;
151
+ return trimmed;
152
+ }
153
+ const allowed = (opts.allowedOrigins ?? []).map(normalizeOrigin).filter((o) => o !== null);
154
+ try {
155
+ const parsed = new URL(trimmed);
156
+ const origin = `${parsed.protocol}//${parsed.host}`.toLowerCase();
157
+ if (!allowed.includes(origin)) return defaultPath;
158
+ if (opts.stripSameOriginHost !== false) {
159
+ return `${parsed.pathname}${parsed.search}${parsed.hash}` || "/";
160
+ }
161
+ return trimmed;
162
+ } catch {
163
+ return defaultPath;
164
+ }
165
+ }
166
+
167
+ // src/server/ledvery.ts
168
+ function createLedveryRoutes(config) {
169
+ const client = new ledvery.LedveryClient({
170
+ issuer: config.issuer,
171
+ clientId: config.clientId,
172
+ clientSecret: config.clientSecret,
173
+ redirectUri: config.redirectUri,
174
+ fetch: config.fetch
175
+ });
176
+ const postLoginRedirect = config.postLoginRedirect || "/";
177
+ const postLogoutRedirect = config.postLogoutRedirect || "/";
178
+ const defaultScope = config.defaultScope || "openid email profile";
179
+ async function handleLogin(request) {
180
+ const url = new URL(request.url);
181
+ const returnTo = url.searchParams.get("returnTo");
182
+ const provider = url.searchParams.get("provider");
183
+ const validatedReturnTo = validateSafeRedirect(returnTo, { defaultPath: postLoginRedirect });
184
+ const authResult = await client.createAuthorizationUrl({
185
+ scope: defaultScope,
186
+ extraParams: provider ? { upstream: provider } : void 0
187
+ });
188
+ const response = new Response(null, {
189
+ status: 302,
190
+ headers: { Location: authResult.url }
191
+ });
192
+ setLedveryFlowCookies(response, {
193
+ state: authResult.state,
194
+ codeVerifier: authResult.codeVerifier,
195
+ nonce: authResult.nonce
196
+ });
197
+ if (validatedReturnTo !== postLoginRedirect) {
198
+ response.headers.append(
199
+ "Set-Cookie",
200
+ `sm_ledvery_return_to=${encodeURIComponent(validatedReturnTo)}; Path=/; Max-Age=600; HttpOnly; SameSite=Lax${process.env.NODE_ENV === "production" ? "; Secure" : ""}`
201
+ );
202
+ }
203
+ return response;
204
+ }
205
+ async function handleCallback(request) {
206
+ const url = new URL(request.url);
207
+ const code = url.searchParams.get("code");
208
+ const callbackState = url.searchParams.get("state");
209
+ if (!code) {
210
+ const error = url.searchParams.get("error");
211
+ const errorDesc = url.searchParams.get("error_description");
212
+ return new Response(
213
+ JSON.stringify({ error: error || "missing_code", message: errorDesc || "No authorization code in callback" }),
214
+ { status: 403, headers: { "Content-Type": "application/json" } }
215
+ );
216
+ }
217
+ const flowResult = validateAndConsumeLedveryFlowCookies(request, callbackState);
218
+ if (typeof flowResult === "string") {
219
+ return new Response(
220
+ JSON.stringify({ error: "state_mismatch", message: flowResult }),
221
+ { status: 403, headers: { "Content-Type": "application/json" } }
222
+ );
223
+ }
224
+ let session;
225
+ try {
226
+ session = await client.exchangeCode({
227
+ code,
228
+ codeVerifier: flowResult.codeVerifier,
229
+ receivedState: callbackState,
230
+ expectedState: getCookieValue(request, "sm_ledvery_state"),
231
+ expectedNonce: flowResult.nonce
232
+ });
233
+ } catch (err) {
234
+ return new Response(
235
+ JSON.stringify({ error: "code_exchange_failed", message: err instanceof Error ? err.message : "Code exchange failed" }),
236
+ { status: 403, headers: { "Content-Type": "application/json" } }
237
+ );
238
+ }
239
+ const returnTo = getCookieValue(request, "sm_ledvery_return_to");
240
+ const redirectTo = validateSafeRedirect(returnTo, { defaultPath: postLoginRedirect });
241
+ const response = new Response(null, {
242
+ status: 302,
243
+ headers: { Location: redirectTo }
244
+ });
245
+ setLedverySession(response, {
246
+ idToken: session.idToken,
247
+ accessToken: session.accessToken,
248
+ claims: session.claims,
249
+ expiresAt: session.expiresAt.toISOString()
250
+ }, {
251
+ storeAccessToken: config.storeAccessToken,
252
+ cookies: config.cookies
253
+ });
254
+ clearLedveryFlowCookies(response);
255
+ response.headers.append("Set-Cookie", "sm_ledvery_return_to=; Path=/; Max-Age=0; HttpOnly; SameSite=Lax");
256
+ return response;
257
+ }
258
+ function handleSession(request) {
259
+ const session = getLedverySession(request);
260
+ if (!session) {
261
+ return new Response(JSON.stringify({ session: null }), {
262
+ status: 200,
263
+ headers: { "Content-Type": "application/json" }
264
+ });
265
+ }
266
+ const expiresAt = new Date(session.expiresAt);
267
+ if (expiresAt <= /* @__PURE__ */ new Date()) {
268
+ return new Response(JSON.stringify({ session: null }), {
269
+ status: 200,
270
+ headers: { "Content-Type": "application/json" }
271
+ });
272
+ }
273
+ return new Response(
274
+ JSON.stringify({
275
+ session: {
276
+ sub: session.claims.sub,
277
+ email: session.claims.email,
278
+ email_verified: session.claims.email_verified,
279
+ name: session.claims.name,
280
+ idp: session.claims.idp,
281
+ expiresAt: session.expiresAt
282
+ }
283
+ }),
284
+ { status: 200, headers: { "Content-Type": "application/json" } }
285
+ );
286
+ }
287
+ function handleLogout(request) {
288
+ const session = getLedverySession(request);
289
+ const idToken = session?.idToken;
290
+ if (config.gatewayUrl) {
291
+ let rpLogoutUrl = `${config.gatewayUrl}/v1/auth/oauth/ledvery/logout?post_logout_redirect_uri=${encodeURIComponent(postLogoutRedirect)}`;
292
+ if (idToken) {
293
+ rpLogoutUrl += `&id_token_hint=${encodeURIComponent(idToken)}`;
294
+ }
295
+ const response2 = new Response(null, {
296
+ status: 302,
297
+ headers: { Location: rpLogoutUrl }
298
+ });
299
+ clearLedverySession(response2);
300
+ return response2;
301
+ }
302
+ const response = new Response(null, {
303
+ status: 302,
304
+ headers: { Location: postLogoutRedirect }
305
+ });
306
+ clearLedverySession(response);
307
+ return response;
308
+ }
309
+ async function handler(request, context) {
310
+ const params = await context.params;
311
+ const action = params.action?.[0] || "";
312
+ switch (action) {
313
+ case "login":
314
+ return handleLogin(request);
315
+ case "callback":
316
+ return handleCallback(request);
317
+ case "session":
318
+ return handleSession(request);
319
+ case "logout":
320
+ return handleLogout(request);
321
+ default:
322
+ return new Response(
323
+ JSON.stringify({ error: "not_found", message: `Unknown Ledvery route: ${action}` }),
324
+ { status: 404, headers: { "Content-Type": "application/json" } }
325
+ );
326
+ }
327
+ }
328
+ return {
329
+ GET: handler,
330
+ POST: handler
331
+ };
332
+ }
333
+ function getCookieValue(request, name) {
334
+ const header = request.headers.get("cookie") || "";
335
+ const match = header.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
336
+ return match ? decodeURIComponent(match[1]) : void 0;
337
+ }
338
+
339
+ exports.createLedveryRoutes = createLedveryRoutes;
340
+ exports.getLedverySession = getLedverySession;