@sylphx/sdk 0.10.7 → 0.11.0

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