@sylphx/sdk 0.15.0 → 0.15.2

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.
@@ -649,6 +649,19 @@ interface AuthCookiesData {
649
649
  /** Expiry timestamp from USER cookie */
650
650
  expiresAt: number | null;
651
651
  }
652
+ /**
653
+ * Decode a cookie value without throwing on malformed percent-encoding.
654
+ */
655
+ declare function decodeCookieValue(value: string): string;
656
+ /**
657
+ * Read the last value for a cookie name from a raw Cookie header.
658
+ *
659
+ * Browsers can legitimately send duplicate cookie names when an application
660
+ * has migrated between host-only and domain-scoped cookies. RFC 6265 orders
661
+ * same-path duplicates by creation time, so the most recently set cookie is the
662
+ * right value for auth session recovery.
663
+ */
664
+ declare function readCookieValueFromHeader(cookieHeader: string | null | undefined, name: string): string | null;
652
665
  /**
653
666
  * Get auth cookies from the request
654
667
  *
@@ -703,4 +716,4 @@ declare function clearAuthCookiesMiddleware(response: NextResponse, namespace: s
703
716
  */
704
717
  declare function parseUserCookie(value: string): UserCookieData | null;
705
718
 
706
- export { ACTIVE_ORG_COOKIE_OPTIONS, ACTIVE_ORG_LIFETIME, type AuthCookiesData, type AuthResult, REFRESH_TOKEN_LIFETIME, SECURE_COOKIE_OPTIONS, SESSION_TOKEN_LIFETIME, SESSION_TOKEN_LIFETIME_MS, type SylphxMiddlewareConfig, type SylphxOrganizationContextConfig, TOKEN_EXPIRY_BUFFER_MS, USER_COOKIE_OPTIONS, type UserCookieData, auth, clearAuthCookies, clearAuthCookiesMiddleware, configureServer, createMatcher, createSylphxMiddleware, currentUser, currentUserId, decodeUserId, encodeUserId, getAuthCookies, getAuthorizationUrl, getCookieNames, getNamespace, getSessionToken, handleCallback, hasRefreshToken, isSessionExpired, parseUserCookie, setAuthCookies, setAuthCookiesMiddleware, signOut, sylphxMiddleware, syncAuthToCookies };
719
+ export { ACTIVE_ORG_COOKIE_OPTIONS, ACTIVE_ORG_LIFETIME, type AuthCookiesData, type AuthResult, REFRESH_TOKEN_LIFETIME, SECURE_COOKIE_OPTIONS, SESSION_TOKEN_LIFETIME, SESSION_TOKEN_LIFETIME_MS, type SylphxMiddlewareConfig, type SylphxOrganizationContextConfig, TOKEN_EXPIRY_BUFFER_MS, USER_COOKIE_OPTIONS, type UserCookieData, auth, clearAuthCookies, clearAuthCookiesMiddleware, configureServer, createMatcher, createSylphxMiddleware, currentUser, currentUserId, decodeCookieValue, decodeUserId, encodeUserId, getAuthCookies, getAuthorizationUrl, getCookieNames, getNamespace, getSessionToken, handleCallback, hasRefreshToken, isSessionExpired, parseUserCookie, readCookieValueFromHeader, setAuthCookies, setAuthCookiesMiddleware, signOut, sylphxMiddleware, syncAuthToCookies };
@@ -230,6 +230,26 @@ var ACTIVE_ORG_COOKIE_OPTIONS = {
230
230
  ...SECURE_COOKIE_OPTIONS,
231
231
  httpOnly: true
232
232
  };
233
+ function decodeCookieValue(value) {
234
+ try {
235
+ return decodeURIComponent(value);
236
+ } catch {
237
+ return value;
238
+ }
239
+ }
240
+ function readCookieValueFromHeader(cookieHeader, name) {
241
+ if (!cookieHeader) return null;
242
+ let value = null;
243
+ for (const cookie of cookieHeader.split(";")) {
244
+ const trimmed = cookie.trim();
245
+ const eq = trimmed.indexOf("=");
246
+ if (eq === -1) continue;
247
+ if (trimmed.slice(0, eq) === name) {
248
+ value = decodeCookieValue(trimmed.slice(eq + 1));
249
+ }
250
+ }
251
+ return value;
252
+ }
233
253
  async function getAuthCookies(namespace) {
234
254
  const cookieStore = await cookies();
235
255
  const names = getCookieNames(namespace);
@@ -670,6 +690,19 @@ function clearOAuthPkceCookie(response, ctx) {
670
690
  path: ctx.config.authPrefix
671
691
  });
672
692
  }
693
+ function readRequestCookieValue(request, name) {
694
+ const values = request.cookies.getAll(name);
695
+ if (values.length > 0) {
696
+ const value = values[values.length - 1]?.value;
697
+ return value === void 0 ? void 0 : decodeCookieValue(value);
698
+ }
699
+ const cookie = request.cookies.get(name);
700
+ if (cookie) return decodeCookieValue(cookie.value);
701
+ return readCookieValueFromHeader(request.headers.get("cookie"), name) ?? void 0;
702
+ }
703
+ function hasRequestCookie(request, name) {
704
+ return readRequestCookieValue(request, name) !== void 0;
705
+ }
673
706
  function isTwoFactorLoginResponse(data) {
674
707
  return typeof data === "object" && data !== null && data.requiresTwoFactor === true && typeof data.userId === "string";
675
708
  }
@@ -702,7 +735,7 @@ function setRestoredSessionCookies(response, ctx, restored) {
702
735
  }
703
736
  }
704
737
  async function refreshSessionFromCookie(request, ctx, previousSessionToken) {
705
- const refreshToken = request.cookies.get(ctx.cookieNames.REFRESH)?.value;
738
+ const refreshToken = readRequestCookieValue(request, ctx.cookieNames.REFRESH);
706
739
  if (!refreshToken) return null;
707
740
  const refreshedTokens = await refreshTokens(refreshToken, ctx);
708
741
  if (!refreshedTokens) return null;
@@ -799,8 +832,8 @@ async function handleSession(request, ctx) {
799
832
  if (request.method !== "GET") {
800
833
  return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
801
834
  }
802
- const sessionToken = request.cookies.get(ctx.cookieNames.SESSION)?.value;
803
- const userCookie = parseUserCookie2(request.cookies.get(ctx.cookieNames.USER)?.value);
835
+ const sessionToken = readRequestCookieValue(request, ctx.cookieNames.SESSION);
836
+ const userCookie = parseUserCookie2(readRequestCookieValue(request, ctx.cookieNames.USER));
804
837
  if (sessionToken && !isTokenExpired(sessionToken) && userCookie?.user) {
805
838
  return NextResponse.json(sessionMetadataBody(sessionToken, userCookie.user));
806
839
  }
@@ -813,7 +846,7 @@ async function handleSession(request, ctx) {
813
846
  return response2;
814
847
  }
815
848
  const response = NextResponse.json({ success: true, session: null, user: null });
816
- if (sessionToken || request.cookies.has(ctx.cookieNames.REFRESH) || userCookie) {
849
+ if (sessionToken || hasRequestCookie(request, ctx.cookieNames.REFRESH) || userCookie) {
817
850
  clearAuthCookiesMiddleware(response, ctx.namespace);
818
851
  }
819
852
  return response;
@@ -999,7 +1032,7 @@ async function handleCallback(request, ctx) {
999
1032
  }
1000
1033
  async function handleSignOut(request, ctx) {
1001
1034
  ctx.log("Signout");
1002
- const refreshToken = request.cookies.get(ctx.cookieNames.REFRESH)?.value;
1035
+ const refreshToken = readRequestCookieValue(request, ctx.cookieNames.REFRESH);
1003
1036
  if (refreshToken) {
1004
1037
  try {
1005
1038
  await fetch(`${ctx.platformUrl}/v1/auth/revoke`, {
@@ -1021,7 +1054,7 @@ async function handleSignOut(request, ctx) {
1021
1054
  }
1022
1055
  async function handleToken(request, ctx) {
1023
1056
  ctx.log("Token request");
1024
- const sessionToken = request.cookies.get(ctx.cookieNames.SESSION)?.value;
1057
+ const sessionToken = readRequestCookieValue(request, ctx.cookieNames.SESSION);
1025
1058
  if (sessionToken && !isTokenExpired(sessionToken)) {
1026
1059
  ctx.log("Token returned");
1027
1060
  return NextResponse.json({ accessToken: sessionToken });
@@ -1038,7 +1071,7 @@ async function handleToken(request, ctx) {
1038
1071
  { error: sessionToken ? "Session expired" : "Not authenticated", accessToken: null },
1039
1072
  { status: 401 }
1040
1073
  );
1041
- if (sessionToken || request.cookies.has(ctx.cookieNames.REFRESH)) {
1074
+ if (sessionToken || hasRequestCookie(request, ctx.cookieNames.REFRESH)) {
1042
1075
  clearAuthCookiesMiddleware(response, ctx.namespace);
1043
1076
  }
1044
1077
  return response;
@@ -1071,17 +1104,10 @@ function parseUserCookie2(value) {
1071
1104
  return null;
1072
1105
  }
1073
1106
  }
1074
- function decodeCookieValue(value) {
1075
- try {
1076
- return decodeURIComponent(value);
1077
- } catch {
1078
- return value;
1079
- }
1080
- }
1081
1107
  function readFirstCookieValue(request, names) {
1082
1108
  for (const name of names) {
1083
- const value = request.cookies.get(name)?.value;
1084
- if (value) return decodeCookieValue(value);
1109
+ const value = readRequestCookieValue(request, name);
1110
+ if (value) return value;
1085
1111
  }
1086
1112
  return null;
1087
1113
  }
@@ -1214,9 +1240,22 @@ async function handleSwitchOrg(request, ctx) {
1214
1240
  if (request.method !== "POST") {
1215
1241
  return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
1216
1242
  }
1217
- const sessionToken = request.cookies.get(ctx.cookieNames.SESSION)?.value;
1218
- if (!sessionToken || isTokenExpired(sessionToken)) {
1219
- return NextResponse.json({ error: "Not authenticated", accessToken: null }, { status: 401 });
1243
+ const currentSessionToken = readRequestCookieValue(request, ctx.cookieNames.SESSION);
1244
+ let sessionToken = currentSessionToken && !isTokenExpired(currentSessionToken) ? currentSessionToken : null;
1245
+ let restoredBaseSession = null;
1246
+ if (!sessionToken) {
1247
+ restoredBaseSession = await refreshSessionFromCookie(request, ctx, currentSessionToken);
1248
+ sessionToken = restoredBaseSession?.tokens.accessToken ?? null;
1249
+ }
1250
+ if (!sessionToken) {
1251
+ const response = NextResponse.json(
1252
+ { error: "Not authenticated", accessToken: null },
1253
+ { status: 401 }
1254
+ );
1255
+ if (currentSessionToken || hasRequestCookie(request, ctx.cookieNames.REFRESH)) {
1256
+ clearAuthCookiesMiddleware(response, ctx.namespace);
1257
+ }
1258
+ return response;
1220
1259
  }
1221
1260
  let orgId = null;
1222
1261
  let requestedOrgSlug = null;
@@ -1258,11 +1297,17 @@ async function handleSwitchOrg(request, ctx) {
1258
1297
  user: data.user ?? null,
1259
1298
  organization: activeOrganization
1260
1299
  });
1300
+ if (restoredBaseSession) {
1301
+ setAuthCookiesMiddleware(response, ctx.namespace, restoredBaseSession.tokens);
1302
+ if (restoredBaseSession.activeOrganization) {
1303
+ setActiveOrganizationCookies(response, ctx, restoredBaseSession.activeOrganization);
1304
+ }
1305
+ }
1261
1306
  response.cookies.set(ctx.cookieNames.SESSION, accessToken, {
1262
1307
  ...SECURE_COOKIE_OPTIONS,
1263
1308
  maxAge: expiresIn
1264
1309
  });
1265
- const existingUser = parseUserCookie2(request.cookies.get(ctx.cookieNames.USER)?.value);
1310
+ const existingUser = parseUserCookie2(readRequestCookieValue(request, ctx.cookieNames.USER));
1266
1311
  const user = data.user ?? existingUser?.user;
1267
1312
  if (user) {
1268
1313
  response.cookies.set(
@@ -1487,8 +1532,8 @@ function createSylphxMiddleware(userConfig = {}) {
1487
1532
  if (pathname === `${config.authPrefix}/switch-org`) {
1488
1533
  return handleSwitchOrg(request, ctx);
1489
1534
  }
1490
- const sessionToken = request.cookies.get(cookieNames.SESSION)?.value;
1491
- const refreshToken = request.cookies.get(cookieNames.REFRESH)?.value;
1535
+ const sessionToken = readRequestCookieValue(request, cookieNames.SESSION);
1536
+ const refreshToken = readRequestCookieValue(request, cookieNames.REFRESH);
1492
1537
  const hasValidSession = sessionToken && !isTokenExpired(sessionToken);
1493
1538
  const response = NextResponse.next({ request: { headers: request.headers } });
1494
1539
  let isAuthenticated = hasValidSession;
@@ -3009,6 +3054,7 @@ export {
3009
3054
  createSylphxMiddleware,
3010
3055
  currentUser,
3011
3056
  currentUserId,
3057
+ decodeCookieValue,
3012
3058
  decodeUserId,
3013
3059
  encodeUserId,
3014
3060
  getAuthCookies,
@@ -3020,6 +3066,7 @@ export {
3020
3066
  hasRefreshToken,
3021
3067
  isSessionExpired,
3022
3068
  parseUserCookie,
3069
+ readCookieValueFromHeader,
3023
3070
  setAuthCookies,
3024
3071
  setAuthCookiesMiddleware,
3025
3072
  signOut,