@sylphx/sdk 0.10.6 → 0.10.7

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.
@@ -64,14 +64,18 @@ interface SylphxMiddlewareConfig {
64
64
  afterSignInUrl?: string;
65
65
  /**
66
66
  * Auth routes prefix. Routes are mounted at:
67
+ * - {prefix}/register — email/password registration handler
67
68
  * - {prefix}/login — credentials login handler
69
+ * - {prefix}/verify-email — email verification handler
68
70
  * - {prefix}/oauth-providers — enabled social login providers
69
71
  * - {prefix}/oauth/authorize — social login start handler
70
72
  * - {prefix}/callback — OAuth callback handler
71
73
  * - {prefix}/passkey/options — passkey login challenge handler
72
74
  * - {prefix}/passkey/authenticate — passkey login verification handler
75
+ * - {prefix}/verify-2fa — TOTP/backup-code verification handler
73
76
  * - {prefix}/forgot-password — password reset email handler
74
77
  * - {prefix}/reset-password — password reset verification handler
78
+ * - {prefix}/session — safe session metadata handler
75
79
  * - {prefix}/signout — Sign out handler
76
80
  *
77
81
  * @default '/auth'
@@ -634,12 +634,30 @@ function isTwoFactorLoginResponse(data) {
634
634
  function isOAuthAuthorizeResponse(data) {
635
635
  return typeof data === "object" && data !== null && typeof data.authorization_url === "string";
636
636
  }
637
+ function authCookieJsonResponse(ctx, data) {
638
+ const response = NextResponse.json({ success: true, user: data.user });
639
+ setAuthCookiesMiddleware(response, ctx.namespace, data);
640
+ return response;
641
+ }
637
642
  function headersForPlatformAuth(ctx) {
638
643
  return {
639
644
  "Content-Type": "application/json",
640
645
  "x-app-secret": ctx.secretKey
641
646
  };
642
647
  }
648
+ async function handleRegister(request, ctx) {
649
+ if (request.method !== "POST") {
650
+ return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
651
+ }
652
+ const body = await parseJsonObject(request);
653
+ const res = await fetch(`${ctx.platformUrl}/v1/auth/register`, {
654
+ method: "POST",
655
+ headers: headersForPlatformAuth(ctx),
656
+ body: JSON.stringify(body ?? {})
657
+ });
658
+ const data = await res.json().catch(() => ({}));
659
+ return NextResponse.json(data, { status: res.status });
660
+ }
643
661
  async function handleLogin(request, ctx) {
644
662
  if (request.method !== "POST") {
645
663
  return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
@@ -665,9 +683,78 @@ async function handleLogin(request, ctx) {
665
683
  if (!isTokenResponse(data)) {
666
684
  return NextResponse.json({ error: "invalid_response" }, { status: 502 });
667
685
  }
668
- const response = NextResponse.json({ success: true, user: data.user });
669
- setAuthCookiesMiddleware(response, ctx.namespace, data);
670
- return response;
686
+ return authCookieJsonResponse(ctx, data);
687
+ }
688
+ async function handleVerifyEmail(request, ctx) {
689
+ if (request.method !== "POST") {
690
+ return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
691
+ }
692
+ const body = await parseJsonObject(request);
693
+ const token = typeof body?.token === "string" ? body.token : null;
694
+ if (!token) {
695
+ return NextResponse.json({ error: "token is required" }, { status: 400 });
696
+ }
697
+ const res = await fetch(`${ctx.platformUrl}/v1/auth/verify-email`, {
698
+ method: "POST",
699
+ headers: headersForPlatformAuth(ctx),
700
+ body: JSON.stringify({ token })
701
+ });
702
+ const data = await res.json().catch(() => ({}));
703
+ return NextResponse.json(data, { status: res.status });
704
+ }
705
+ async function handleVerifyTwoFactor(request, ctx) {
706
+ if (request.method !== "POST") {
707
+ return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
708
+ }
709
+ const body = await parseJsonObject(request);
710
+ const userId = typeof body?.userId === "string" ? body.userId : null;
711
+ const code = typeof body?.code === "string" ? body.code : null;
712
+ if (!userId || !code) {
713
+ return NextResponse.json({ error: "userId and code are required" }, { status: 400 });
714
+ }
715
+ const res = await fetch(`${ctx.platformUrl}/v1/auth/verify-2fa`, {
716
+ method: "POST",
717
+ headers: headersForPlatformAuth(ctx),
718
+ body: JSON.stringify({ userId, code })
719
+ });
720
+ const data = await res.json().catch(() => ({}));
721
+ if (!res.ok) {
722
+ return NextResponse.json(data, { status: res.status });
723
+ }
724
+ if (!isTokenResponse(data)) {
725
+ return NextResponse.json({ error: "invalid_response" }, { status: 502 });
726
+ }
727
+ return authCookieJsonResponse(ctx, data);
728
+ }
729
+ function handleSession(request, ctx) {
730
+ if (request.method !== "GET") {
731
+ return NextResponse.json({ error: "Method not allowed" }, { status: 405 });
732
+ }
733
+ const sessionToken = request.cookies.get(ctx.cookieNames.SESSION)?.value;
734
+ const userCookieValue = request.cookies.get(ctx.cookieNames.USER)?.value;
735
+ let userCookie = userCookieValue ? parseUserCookie2(userCookieValue) : null;
736
+ if (!userCookie && userCookieValue) {
737
+ try {
738
+ userCookie = parseUserCookie2(decodeURIComponent(userCookieValue));
739
+ } catch {
740
+ userCookie = null;
741
+ }
742
+ }
743
+ if (!sessionToken || isTokenExpired(sessionToken) || !userCookie?.user) {
744
+ return NextResponse.json({ success: true, session: null, user: null });
745
+ }
746
+ const payload = decodeJwtPayload(sessionToken);
747
+ const userId = payload?.sub ?? userCookie.user.id;
748
+ const expiresAt = typeof payload?.exp === "number" ? new Date(payload.exp * 1e3).toISOString() : new Date(userCookie.expiresAt).toISOString();
749
+ return NextResponse.json({
750
+ success: true,
751
+ session: {
752
+ id: `platform:${userId}`,
753
+ userId,
754
+ expiresAt
755
+ },
756
+ user: userCookie.user
757
+ });
671
758
  }
672
759
  async function handleOAuthProviders(ctx) {
673
760
  const res = await fetch(`${ctx.platformUrl}/v1/auth/oauth-providers`, {
@@ -747,9 +834,7 @@ async function handlePasskeyAuthenticate(request, ctx) {
747
834
  if (!isTokenResponse(data)) {
748
835
  return NextResponse.json({ error: "invalid_response" }, { status: 502 });
749
836
  }
750
- const response = NextResponse.json({ success: true, user: data.user });
751
- setAuthCookiesMiddleware(response, ctx.namespace, data);
752
- return response;
837
+ return authCookieJsonResponse(ctx, data);
753
838
  }
754
839
  async function handleForgotPassword(request, ctx) {
755
840
  if (request.method !== "POST") {
@@ -1091,12 +1176,15 @@ async function handleSwitchOrg(request, ctx) {
1091
1176
  }
1092
1177
  const expiresIn = resolveOrgScopedTokenExpiresIn(data);
1093
1178
  const expiresAt = Date.now() + expiresIn * 1e3;
1179
+ const payload = decodeJwtPayload(accessToken);
1180
+ const activeOrganization = {
1181
+ id: payload?.org_id ?? orgId,
1182
+ slug: payload?.org_slug ?? requestedOrgSlug
1183
+ };
1094
1184
  const response = NextResponse.json({
1095
- accessToken,
1096
- token: accessToken,
1097
- expiresIn,
1098
- tokenType: resolveOrgScopedTokenType(data),
1099
- user: data.user ?? null
1185
+ success: true,
1186
+ user: data.user ?? null,
1187
+ organization: activeOrganization
1100
1188
  });
1101
1189
  response.cookies.set(ctx.cookieNames.SESSION, accessToken, {
1102
1190
  ...SECURE_COOKIE_OPTIONS,
@@ -1117,11 +1205,7 @@ async function handleSwitchOrg(request, ctx) {
1117
1205
  }
1118
1206
  );
1119
1207
  }
1120
- const payload = decodeJwtPayload(accessToken);
1121
- setActiveOrganizationCookies(response, ctx, {
1122
- id: payload?.org_id ?? orgId,
1123
- slug: payload?.org_slug ?? requestedOrgSlug
1124
- });
1208
+ setActiveOrganizationCookies(response, ctx, activeOrganization);
1125
1209
  ctx.log("Switch org success", { orgId });
1126
1210
  return response;
1127
1211
  } catch (err) {
@@ -1267,12 +1351,18 @@ function createSylphxMiddleware(userConfig = {}) {
1267
1351
  if (pathname.includes(".") || pathname.startsWith("/_next")) {
1268
1352
  return NextResponse.next();
1269
1353
  }
1354
+ if (pathname === `${config.authPrefix}/register`) {
1355
+ return handleRegister(request, ctx);
1356
+ }
1270
1357
  if (pathname === `${config.authPrefix}/callback`) {
1271
1358
  return handleCallback(request, ctx);
1272
1359
  }
1273
1360
  if (pathname === `${config.authPrefix}/login`) {
1274
1361
  return handleLogin(request, ctx);
1275
1362
  }
1363
+ if (pathname === `${config.authPrefix}/verify-email`) {
1364
+ return handleVerifyEmail(request, ctx);
1365
+ }
1276
1366
  if (pathname === `${config.authPrefix}/oauth-providers`) {
1277
1367
  return handleOAuthProviders(ctx);
1278
1368
  }
@@ -1285,12 +1375,18 @@ function createSylphxMiddleware(userConfig = {}) {
1285
1375
  if (pathname === `${config.authPrefix}/passkey/authenticate`) {
1286
1376
  return handlePasskeyAuthenticate(request, ctx);
1287
1377
  }
1378
+ if (pathname === `${config.authPrefix}/verify-2fa`) {
1379
+ return handleVerifyTwoFactor(request, ctx);
1380
+ }
1288
1381
  if (pathname === `${config.authPrefix}/forgot-password`) {
1289
1382
  return handleForgotPassword(request, ctx);
1290
1383
  }
1291
1384
  if (pathname === `${config.authPrefix}/reset-password`) {
1292
1385
  return handleResetPassword(request, ctx);
1293
1386
  }
1387
+ if (pathname === `${config.authPrefix}/session`) {
1388
+ return handleSession(request, ctx);
1389
+ }
1294
1390
  if (pathname === `${config.authPrefix}/signout`) {
1295
1391
  return handleSignOut(request, ctx);
1296
1392
  }