@sylphx/sdk 0.10.7 → 0.11.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.
@@ -116,6 +116,25 @@ interface SylphxMiddlewareConfig {
116
116
  * @default process.env.SYLPHX_SECRET_URL
117
117
  */
118
118
  secretUrl?: string;
119
+ /**
120
+ * Publishable key for browser-safe auth routes.
121
+ *
122
+ * Public auth handlers (`/auth/login`, `/auth/oauth-providers`,
123
+ * `/auth/oauth/authorize`, password reset, passkeys) call runtime routes
124
+ * guarded by projectAuth, which accepts pk_* only.
125
+ *
126
+ * @default credential parsed from process.env.SYLPHX_URL or
127
+ * process.env.NEXT_PUBLIC_SYLPHX_URL
128
+ */
129
+ publishableKey?: string;
130
+ /**
131
+ * Public connection URL for browser-safe auth routes.
132
+ *
133
+ * Format: sylphx://pk_<env>_<hex>@<tenant>.api.sylphx.com
134
+ *
135
+ * @default process.env.SYLPHX_URL or process.env.NEXT_PUBLIC_SYLPHX_URL
136
+ */
137
+ publicUrl?: string;
119
138
  /**
120
139
  * Platform URL for API calls.
121
140
  * Override for self-hosted or same-origin deployments.
@@ -3,7 +3,9 @@ import { NextResponse } from "next/server";
3
3
 
4
4
  // src/constants.ts
5
5
  var ENV_URL = "SYLPHX_URL";
6
+ var ENV_PUBLIC_URL = "NEXT_PUBLIC_SYLPHX_URL";
6
7
  var ENV_SECRET_URL = "SYLPHX_SECRET_URL";
8
+ var SDK_API_PATH = `/v1`;
7
9
  var DEFAULT_SDK_API_HOST = "api.sylphx.com";
8
10
  var SDK_PLATFORM = typeof window !== "undefined" ? "browser" : typeof process !== "undefined" && process.versions?.node ? "node" : "unknown";
9
11
  var TOKEN_EXPIRY_BUFFER_MS = 3e4;
@@ -127,6 +129,12 @@ function validateKeyForType(key, keyType, pattern, envVarName) {
127
129
  function validatePublicKey(key) {
128
130
  return validateKeyForType(key, "publicKey", PUBLIC_KEY_PATTERN, "publishable credential");
129
131
  }
132
+ function validateAndSanitizePublicKey(key) {
133
+ const result = validatePublicKey(key);
134
+ if (!result.valid) throw new Error(result.error);
135
+ if (result.warning) console.warn(result.warning);
136
+ return result.sanitizedKey;
137
+ }
130
138
  function validateAppId(key) {
131
139
  return validateKeyForType(key, "appId", APP_ID_PATTERN, "NEXT_PUBLIC_SYLPHX_APP_ID");
132
140
  }
@@ -427,6 +435,11 @@ function secretKeyFromConnectionUrl(value) {
427
435
  if (!parsed || parsed.credentialType !== "sk") return null;
428
436
  return parsed.credential;
429
437
  }
438
+ function publishableKeyFromConnectionUrl(value) {
439
+ const parsed = parseConnection(value);
440
+ if (!parsed || parsed.credentialType !== "pk") return null;
441
+ return parsed.credential;
442
+ }
430
443
  function resolveNextjsPlatformUrl(options) {
431
444
  if (options.explicitPlatformUrl) return normalizePlatformUrl(options.explicitPlatformUrl);
432
445
  const fromExplicitSecretUrl = platformUrlFromConnectionUrl(options.secretUrl);
@@ -451,6 +464,16 @@ function resolveNextjsSecretKey(options) {
451
464
  if (fromGenericUrl) return fromGenericUrl;
452
465
  return null;
453
466
  }
467
+ function resolveNextjsPublishableKey(options) {
468
+ if (options.explicitPublishableKey?.trim()) return options.explicitPublishableKey.trim();
469
+ const fromExplicitPublicUrl = publishableKeyFromConnectionUrl(options.explicitPublicUrl);
470
+ if (fromExplicitPublicUrl) return fromExplicitPublicUrl;
471
+ const fromPublicUrl = publishableKeyFromConnectionUrl(process.env[ENV_URL]);
472
+ if (fromPublicUrl) return fromPublicUrl;
473
+ const fromNextPublicUrl = publishableKeyFromConnectionUrl(process.env[ENV_PUBLIC_URL]);
474
+ if (fromNextPublicUrl) return fromNextPublicUrl;
475
+ return null;
476
+ }
454
477
 
455
478
  // src/nextjs/middleware.ts
456
479
  var OAUTH_PKCE_TTL_SECONDS = 10 * 60;
@@ -639,10 +662,10 @@ function authCookieJsonResponse(ctx, data) {
639
662
  setAuthCookiesMiddleware(response, ctx.namespace, data);
640
663
  return response;
641
664
  }
642
- function headersForPlatformAuth(ctx) {
665
+ function headersForProjectAuth(ctx) {
643
666
  return {
644
667
  "Content-Type": "application/json",
645
- "x-app-secret": ctx.secretKey
668
+ "x-app-secret": ctx.publishableKey
646
669
  };
647
670
  }
648
671
  async function handleRegister(request, ctx) {
@@ -652,7 +675,7 @@ async function handleRegister(request, ctx) {
652
675
  const body = await parseJsonObject(request);
653
676
  const res = await fetch(`${ctx.platformUrl}/v1/auth/register`, {
654
677
  method: "POST",
655
- headers: headersForPlatformAuth(ctx),
678
+ headers: headersForProjectAuth(ctx),
656
679
  body: JSON.stringify(body ?? {})
657
680
  });
658
681
  const data = await res.json().catch(() => ({}));
@@ -670,7 +693,7 @@ async function handleLogin(request, ctx) {
670
693
  }
671
694
  const res = await fetch(`${ctx.platformUrl}/v1/auth/login`, {
672
695
  method: "POST",
673
- headers: headersForPlatformAuth(ctx),
696
+ headers: headersForProjectAuth(ctx),
674
697
  body: JSON.stringify({ email, password })
675
698
  });
676
699
  const data = await res.json().catch(() => ({}));
@@ -696,7 +719,7 @@ async function handleVerifyEmail(request, ctx) {
696
719
  }
697
720
  const res = await fetch(`${ctx.platformUrl}/v1/auth/verify-email`, {
698
721
  method: "POST",
699
- headers: headersForPlatformAuth(ctx),
722
+ headers: headersForProjectAuth(ctx),
700
723
  body: JSON.stringify({ token })
701
724
  });
702
725
  const data = await res.json().catch(() => ({}));
@@ -714,7 +737,7 @@ async function handleVerifyTwoFactor(request, ctx) {
714
737
  }
715
738
  const res = await fetch(`${ctx.platformUrl}/v1/auth/verify-2fa`, {
716
739
  method: "POST",
717
- headers: headersForPlatformAuth(ctx),
740
+ headers: headersForProjectAuth(ctx),
718
741
  body: JSON.stringify({ userId, code })
719
742
  });
720
743
  const data = await res.json().catch(() => ({}));
@@ -759,7 +782,7 @@ function handleSession(request, ctx) {
759
782
  async function handleOAuthProviders(ctx) {
760
783
  const res = await fetch(`${ctx.platformUrl}/v1/auth/oauth-providers`, {
761
784
  method: "GET",
762
- headers: headersForPlatformAuth(ctx)
785
+ headers: headersForProjectAuth(ctx)
763
786
  });
764
787
  const data = await res.json().catch(() => ({}));
765
788
  return NextResponse.json(data, { status: res.status });
@@ -782,7 +805,7 @@ async function handleOAuthAuthorize(request, ctx) {
782
805
  const scopes = Array.isArray(body?.scopes) ? body.scopes.filter((scope) => typeof scope === "string") : void 0;
783
806
  const res = await fetch(`${ctx.platformUrl}/v1/oauth/authorize`, {
784
807
  method: "POST",
785
- headers: headersForPlatformAuth(ctx),
808
+ headers: headersForProjectAuth(ctx),
786
809
  body: JSON.stringify({
787
810
  provider,
788
811
  redirect_uri: redirectUri.toString(),
@@ -811,7 +834,7 @@ async function handlePasskeyOptions(request, ctx) {
811
834
  const body = await parseJsonObject(request);
812
835
  const res = await fetch(`${ctx.platformUrl}/v1/auth/passkey/options`, {
813
836
  method: "POST",
814
- headers: headersForPlatformAuth(ctx),
837
+ headers: headersForProjectAuth(ctx),
815
838
  body: JSON.stringify(body ?? {})
816
839
  });
817
840
  const data = await res.json().catch(() => ({}));
@@ -824,7 +847,7 @@ async function handlePasskeyAuthenticate(request, ctx) {
824
847
  const body = await parseJsonObject(request);
825
848
  const res = await fetch(`${ctx.platformUrl}/v1/auth/passkey/authenticate`, {
826
849
  method: "POST",
827
- headers: headersForPlatformAuth(ctx),
850
+ headers: headersForProjectAuth(ctx),
828
851
  body: JSON.stringify(body ?? {})
829
852
  });
830
853
  const data = await res.json().catch(() => ({}));
@@ -852,7 +875,7 @@ async function handleForgotPassword(request, ctx) {
852
875
  }
853
876
  const res = await fetch(`${ctx.platformUrl}/v1/auth/forgot-password`, {
854
877
  method: "POST",
855
- headers: headersForPlatformAuth(ctx),
878
+ headers: headersForProjectAuth(ctx),
856
879
  body: JSON.stringify({ email, redirectUrl })
857
880
  });
858
881
  const data = await res.json().catch(() => ({}));
@@ -870,7 +893,7 @@ async function handleResetPassword(request, ctx) {
870
893
  }
871
894
  const res = await fetch(`${ctx.platformUrl}/v1/auth/reset-password`, {
872
895
  method: "POST",
873
- headers: headersForPlatformAuth(ctx),
896
+ headers: headersForProjectAuth(ctx),
874
897
  body: JSON.stringify({ token, password })
875
898
  });
876
899
  const data = await res.json().catch(() => ({}));
@@ -1281,6 +1304,21 @@ function createSylphxMiddleware(userConfig = {}) {
1281
1304
  secretKey = validateAndSanitizeSecretKey(rawSecretKey);
1282
1305
  return secretKey;
1283
1306
  }
1307
+ let publishableKey = null;
1308
+ function resolvePublishableKey() {
1309
+ if (publishableKey) return publishableKey;
1310
+ const rawPublishableKey = resolveNextjsPublishableKey({
1311
+ explicitPublishableKey: userConfig.publishableKey,
1312
+ explicitPublicUrl: userConfig.publicUrl
1313
+ });
1314
+ if (!rawPublishableKey) {
1315
+ throw new Error(
1316
+ "[Sylphx] Publishable connection URL is required for public auth routes.\nEither pass publicUrl/publishableKey in config or set SYLPHX_URL/NEXT_PUBLIC_SYLPHX_URL."
1317
+ );
1318
+ }
1319
+ publishableKey = validateAndSanitizePublicKey(rawPublishableKey);
1320
+ return publishableKey;
1321
+ }
1284
1322
  let platformUrl;
1285
1323
  let namespace;
1286
1324
  let cookieNames;
@@ -1328,6 +1366,9 @@ function createSylphxMiddleware(userConfig = {}) {
1328
1366
  get secretKey() {
1329
1367
  return secretKey;
1330
1368
  },
1369
+ get publishableKey() {
1370
+ return resolvePublishableKey();
1371
+ },
1331
1372
  get platformUrl() {
1332
1373
  return platformUrl;
1333
1374
  },
@@ -2661,6 +2702,8 @@ function decodeUserId(prefixedId) {
2661
2702
  }
2662
2703
 
2663
2704
  // src/server/index.ts
2705
+ var PLATFORM_CONSOLE_SLUG = "keen-wave-x3m9p2";
2706
+ var PLATFORM_TENANT_BAAS_URL = `https://${PLATFORM_CONSOLE_SLUG}.${DEFAULT_SDK_API_HOST}${SDK_API_PATH}`;
2664
2707
  function isJwksResponse(data) {
2665
2708
  return typeof data === "object" && data !== null && "keys" in data && Array.isArray(data.keys);
2666
2709
  }