@solvapay/server 1.0.8-preview.1 → 1.0.8-preview.11

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.
package/dist/edge.d.ts CHANGED
@@ -3720,8 +3720,16 @@ declare function handleRouteError(error: unknown, operationName: string, default
3720
3720
  * and name from authenticated requests. Works with any framework that uses
3721
3721
  * the standard Web API Request (Express, Fastify, Next.js, Edge Functions, etc.).
3722
3722
  *
3723
- * Uses dynamic imports to avoid requiring @solvapay/auth at build time,
3724
- * making it suitable for edge runtime environments.
3723
+ * Resolution order:
3724
+ * 1. `x-user-id` header set by Next.js-style middleware (unchanged).
3725
+ * 2. `Authorization: Bearer <jwt>` — verified via HS256 when a secret is
3726
+ * configured (`SOLVAPAY_JWT_SECRET` or `SUPABASE_JWT_SECRET`), or
3727
+ * unverified-decoded when no secret is configured. The unverified path
3728
+ * covers platforms that verify JWTs at the gateway (e.g. Supabase Edge
3729
+ * with `verify_jwt = true`, which is the default) and asymmetric signing
3730
+ * keys (ES256/RS256) that the SDK does not have keys for.
3731
+ * 3. Set `SOLVAPAY_AUTH_STRICT=true` to require cryptographic verification
3732
+ * inside the handler (the unverified-decode fallback is then disabled).
3725
3733
  *
3726
3734
  * @param request - Standard Web API Request object
3727
3735
  * @param options - Configuration options
@@ -4064,6 +4072,29 @@ declare function listPlansCore(request: Request, options?: {
4064
4072
  productRef: string;
4065
4073
  } | ErrorResult>;
4066
4074
 
4075
+ /**
4076
+ * Merchant Helper (Core)
4077
+ *
4078
+ * Generic helper for GET /api/merchant — returns the SDK-facing merchant
4079
+ * identity used by `<MandateText>`, `<CheckoutSummary>`, and trust signals.
4080
+ * Works with standard Web API Request (Express, Fastify, Next.js, Edge).
4081
+ */
4082
+
4083
+ declare function getMerchantCore(_request: Request, options?: {
4084
+ solvaPay?: SolvaPay;
4085
+ }): Promise<SdkMerchantResponse | ErrorResult>;
4086
+
4087
+ /**
4088
+ * Product Helper (Core)
4089
+ *
4090
+ * Generic helper for GET /api/get-product?productRef=...
4091
+ * Returns a single product by reference, used by the `useProduct` React hook.
4092
+ */
4093
+
4094
+ declare function getProductCore(request: Request, options?: {
4095
+ solvaPay?: SolvaPay;
4096
+ }): Promise<SdkProductResponse | ErrorResult>;
4097
+
4067
4098
  interface PurchaseCheckResult {
4068
4099
  customerRef: string;
4069
4100
  email?: string;
@@ -4120,4 +4151,4 @@ declare function verifyWebhook({ body, signature, secret, }: {
4120
4151
  secret: string;
4121
4152
  }): Promise<WebhookEvent>;
4122
4153
 
4123
- export { type AuthenticatedUser, type CreateSolvaPayConfig, type CustomerBalanceResult, type CustomerWebhookObject, type ErrorResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, type LimitPlanSummary, type McpAdapterOptions, type NextAdapterOptions, type PayableFunction, type PayableOptions, type PaywallArgs, PaywallError, type PaywallMetadata, type PaywallStructuredContent, type PaywallToolResult, type PurchaseCheckResult, type RetryOptions, type ServerClientOptions, type SolvaPay, type SolvaPayClient, type WebhookEvent, type WebhookEventForType, type WebhookEventObjectMap, type WebhookEventType, type WebhookProduct, activatePlanCore, cancelPurchaseCore, checkPurchaseCore, createCheckoutSessionCore, createCustomerSessionCore, createPaymentIntentCore, createSolvaPay, createSolvaPayClient, createTopupPaymentIntentCore, getAuthenticatedUserCore, getCustomerBalanceCore, handleRouteError, isErrorResult, listPlansCore, paywallErrorToClientPayload, processPaymentIntentCore, reactivatePurchaseCore, syncCustomerCore, trackUsageCore, verifyWebhook, withRetry };
4154
+ export { type AuthenticatedUser, type CreateSolvaPayConfig, type CustomerBalanceResult, type CustomerWebhookObject, type ErrorResult, type HttpAdapterOptions, type LimitActivationBalance, type LimitActivationProduct, type LimitPlanSummary, type McpAdapterOptions, type NextAdapterOptions, type PayableFunction, type PayableOptions, type PaywallArgs, PaywallError, type PaywallMetadata, type PaywallStructuredContent, type PaywallToolResult, type PurchaseCheckResult, type RetryOptions, type ServerClientOptions, type SolvaPay, type SolvaPayClient, type WebhookEvent, type WebhookEventForType, type WebhookEventObjectMap, type WebhookEventType, type WebhookProduct, activatePlanCore, cancelPurchaseCore, checkPurchaseCore, createCheckoutSessionCore, createCustomerSessionCore, createPaymentIntentCore, createSolvaPay, createSolvaPayClient, createTopupPaymentIntentCore, getAuthenticatedUserCore, getCustomerBalanceCore, getMerchantCore, getProductCore, handleRouteError, isErrorResult, listPlansCore, paywallErrorToClientPayload, processPaymentIntentCore, reactivatePurchaseCore, syncCustomerCore, trackUsageCore, verifyWebhook, withRetry };
package/dist/edge.js CHANGED
@@ -1758,26 +1758,128 @@ function handleRouteError(error, operationName, defaultMessage) {
1758
1758
  }
1759
1759
 
1760
1760
  // src/helpers/auth.ts
1761
+ function base64UrlDecode(input) {
1762
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
1763
+ const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
1764
+ const base64 = padded + padding;
1765
+ if (typeof atob === "function") {
1766
+ const binary = atob(base64);
1767
+ let result = "";
1768
+ for (let i = 0; i < binary.length; i++) {
1769
+ result += String.fromCharCode(binary.charCodeAt(i));
1770
+ }
1771
+ try {
1772
+ return decodeURIComponent(escape(result));
1773
+ } catch {
1774
+ return result;
1775
+ }
1776
+ }
1777
+ const BufferCtor = globalThis.Buffer;
1778
+ if (BufferCtor) {
1779
+ return BufferCtor.from(base64, "base64").toString("utf-8");
1780
+ }
1781
+ throw new Error("No base64 decoder available in this runtime");
1782
+ }
1783
+ function decodeJwtUnverified(token) {
1784
+ const parts = token.split(".");
1785
+ if (parts.length !== 3) return null;
1786
+ try {
1787
+ const json = base64UrlDecode(parts[1]);
1788
+ const payload = JSON.parse(json);
1789
+ if (typeof payload !== "object" || payload === null) return null;
1790
+ return payload;
1791
+ } catch {
1792
+ return null;
1793
+ }
1794
+ }
1795
+ function extractBearerToken(request) {
1796
+ const authHeader = request.headers.get("authorization");
1797
+ if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
1798
+ const token = authHeader.slice(7).trim();
1799
+ return token.length > 0 ? token : null;
1800
+ }
1801
+ function readEnv(name) {
1802
+ const proc = globalThis.process;
1803
+ return proc?.env?.[name];
1804
+ }
1805
+ function isStrictMode() {
1806
+ return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
1807
+ }
1808
+ function getConfiguredSecret() {
1809
+ return readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
1810
+ }
1811
+ async function verifyHs256(token, secret) {
1812
+ try {
1813
+ const { jwtVerify } = await import("./webapi-K5XBCEO6.js");
1814
+ const key = new TextEncoder().encode(secret);
1815
+ const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
1816
+ return payload;
1817
+ } catch {
1818
+ return null;
1819
+ }
1820
+ }
1821
+ function pickName(payload) {
1822
+ const metadataFullName = typeof payload.user_metadata?.full_name === "string" ? payload.user_metadata.full_name : null;
1823
+ const metadataName = typeof payload.user_metadata?.name === "string" ? payload.user_metadata.name : null;
1824
+ const claimName = typeof payload.name === "string" ? payload.name : null;
1825
+ return metadataFullName || metadataName || claimName || null;
1826
+ }
1827
+ function pickEmail(payload) {
1828
+ return typeof payload.email === "string" ? payload.email : null;
1829
+ }
1830
+ function unauthorized(details) {
1831
+ return { error: "Unauthorized", status: 401, details };
1832
+ }
1761
1833
  async function getAuthenticatedUserCore(request, options = {}) {
1762
1834
  try {
1763
- const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
1764
- const userIdOrError = requireUserId(request);
1765
- if (userIdOrError instanceof Response) {
1766
- const clonedResponse = userIdOrError.clone();
1767
- const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
1768
- return {
1769
- error: body.error || "Unauthorized",
1770
- status: userIdOrError.status,
1771
- details: body.error || "Unauthorized"
1772
- };
1835
+ const includeEmail = options.includeEmail !== false;
1836
+ const includeName = options.includeName !== false;
1837
+ const headerUserId = request.headers.get("x-user-id");
1838
+ if (headerUserId) {
1839
+ let email = null;
1840
+ let name = null;
1841
+ if (includeEmail || includeName) {
1842
+ const token2 = extractBearerToken(request);
1843
+ if (token2) {
1844
+ const secret2 = getConfiguredSecret();
1845
+ const payload2 = secret2 ? await verifyHs256(token2, secret2) : isStrictMode() ? null : decodeJwtUnverified(token2);
1846
+ if (payload2) {
1847
+ if (includeEmail) email = pickEmail(payload2);
1848
+ if (includeName) name = pickName(payload2);
1849
+ }
1850
+ }
1851
+ }
1852
+ return { userId: headerUserId, email, name };
1853
+ }
1854
+ const token = extractBearerToken(request);
1855
+ if (!token) {
1856
+ return unauthorized("User ID not found. Ensure middleware is configured.");
1857
+ }
1858
+ const secret = getConfiguredSecret();
1859
+ let payload = null;
1860
+ if (secret) {
1861
+ payload = await verifyHs256(token, secret);
1862
+ if (!payload) {
1863
+ return unauthorized("Invalid or expired authentication token");
1864
+ }
1865
+ } else if (isStrictMode()) {
1866
+ return unauthorized(
1867
+ "Strict auth mode is enabled but no JWT secret is configured. Set SOLVAPAY_JWT_SECRET or SUPABASE_JWT_SECRET."
1868
+ );
1869
+ } else {
1870
+ payload = decodeJwtUnverified(token);
1871
+ if (!payload) {
1872
+ return unauthorized("Malformed authentication token");
1873
+ }
1874
+ }
1875
+ const userId = typeof payload.sub === "string" ? payload.sub : null;
1876
+ if (!userId) {
1877
+ return unauthorized("Authentication token missing subject (sub) claim");
1773
1878
  }
1774
- const userId = userIdOrError;
1775
- const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
1776
- const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
1777
1879
  return {
1778
1880
  userId,
1779
- email,
1780
- name
1881
+ email: includeEmail ? pickEmail(payload) : null,
1882
+ name: includeName ? pickName(payload) : null
1781
1883
  };
1782
1884
  } catch (error) {
1783
1885
  return handleRouteError(error, "Get authenticated user", "Authentication failed");
@@ -2212,6 +2314,76 @@ async function listPlansCore(request, options = {}) {
2212
2314
  }
2213
2315
  }
2214
2316
 
2317
+ // src/helpers/merchant.ts
2318
+ import { getSolvaPayConfig as getSolvaPayConfig3 } from "@solvapay/core";
2319
+ async function getMerchantCore(_request, options = {}) {
2320
+ try {
2321
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2322
+ const config = getSolvaPayConfig3();
2323
+ if (!config.apiKey) return null;
2324
+ return createSolvaPayClient({
2325
+ apiKey: config.apiKey,
2326
+ apiBaseUrl: config.apiBaseUrl
2327
+ });
2328
+ })();
2329
+ if (!apiClient) {
2330
+ return {
2331
+ error: "Server configuration error: SolvaPay secret key not configured",
2332
+ status: 500
2333
+ };
2334
+ }
2335
+ if (!apiClient.getMerchant) {
2336
+ return {
2337
+ error: "Get merchant method not available",
2338
+ status: 500
2339
+ };
2340
+ }
2341
+ const merchant = await apiClient.getMerchant();
2342
+ return merchant;
2343
+ } catch (error) {
2344
+ return handleRouteError(error, "Get merchant", "Failed to fetch merchant");
2345
+ }
2346
+ }
2347
+
2348
+ // src/helpers/product.ts
2349
+ import { getSolvaPayConfig as getSolvaPayConfig4 } from "@solvapay/core";
2350
+ async function getProductCore(request, options = {}) {
2351
+ try {
2352
+ const url = new URL(request.url);
2353
+ const productRef = url.searchParams.get("productRef");
2354
+ if (!productRef) {
2355
+ return {
2356
+ error: "Missing required parameter: productRef",
2357
+ status: 400
2358
+ };
2359
+ }
2360
+ const apiClient = options.solvaPay?.apiClient ?? (() => {
2361
+ const config = getSolvaPayConfig4();
2362
+ if (!config.apiKey) return null;
2363
+ return createSolvaPayClient({
2364
+ apiKey: config.apiKey,
2365
+ apiBaseUrl: config.apiBaseUrl
2366
+ });
2367
+ })();
2368
+ if (!apiClient) {
2369
+ return {
2370
+ error: "Server configuration error: SolvaPay secret key not configured",
2371
+ status: 500
2372
+ };
2373
+ }
2374
+ if (!apiClient.getProduct) {
2375
+ return {
2376
+ error: "Get product method not available",
2377
+ status: 500
2378
+ };
2379
+ }
2380
+ const product = await apiClient.getProduct(productRef);
2381
+ return product;
2382
+ } catch (error) {
2383
+ return handleRouteError(error, "Get product", "Failed to fetch product");
2384
+ }
2385
+ }
2386
+
2215
2387
  // src/helpers/purchase.ts
2216
2388
  async function checkPurchaseCore(request, options = {}) {
2217
2389
  try {
@@ -2360,6 +2532,8 @@ export {
2360
2532
  createTopupPaymentIntentCore,
2361
2533
  getAuthenticatedUserCore,
2362
2534
  getCustomerBalanceCore,
2535
+ getMerchantCore,
2536
+ getProductCore,
2363
2537
  handleRouteError,
2364
2538
  isErrorResult,
2365
2539
  listPlansCore,
package/dist/index.cjs CHANGED
@@ -6322,26 +6322,128 @@ function handleRouteError(error, operationName, defaultMessage) {
6322
6322
  }
6323
6323
 
6324
6324
  // src/helpers/auth.ts
6325
+ function base64UrlDecode2(input) {
6326
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
6327
+ const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
6328
+ const base64 = padded + padding;
6329
+ if (typeof atob === "function") {
6330
+ const binary = atob(base64);
6331
+ let result = "";
6332
+ for (let i = 0; i < binary.length; i++) {
6333
+ result += String.fromCharCode(binary.charCodeAt(i));
6334
+ }
6335
+ try {
6336
+ return decodeURIComponent(escape(result));
6337
+ } catch {
6338
+ return result;
6339
+ }
6340
+ }
6341
+ const BufferCtor = globalThis.Buffer;
6342
+ if (BufferCtor) {
6343
+ return BufferCtor.from(base64, "base64").toString("utf-8");
6344
+ }
6345
+ throw new Error("No base64 decoder available in this runtime");
6346
+ }
6347
+ function decodeJwtUnverified(token) {
6348
+ const parts = token.split(".");
6349
+ if (parts.length !== 3) return null;
6350
+ try {
6351
+ const json = base64UrlDecode2(parts[1]);
6352
+ const payload = JSON.parse(json);
6353
+ if (typeof payload !== "object" || payload === null) return null;
6354
+ return payload;
6355
+ } catch {
6356
+ return null;
6357
+ }
6358
+ }
6359
+ function extractBearerToken2(request) {
6360
+ const authHeader = request.headers.get("authorization");
6361
+ if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
6362
+ const token = authHeader.slice(7).trim();
6363
+ return token.length > 0 ? token : null;
6364
+ }
6365
+ function readEnv(name) {
6366
+ const proc = globalThis.process;
6367
+ return proc?.env?.[name];
6368
+ }
6369
+ function isStrictMode() {
6370
+ return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
6371
+ }
6372
+ function getConfiguredSecret() {
6373
+ return readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
6374
+ }
6375
+ async function verifyHs256(token, secret) {
6376
+ try {
6377
+ const { jwtVerify: jwtVerify2 } = await Promise.resolve().then(() => (init_webapi(), webapi_exports));
6378
+ const key = new TextEncoder().encode(secret);
6379
+ const { payload } = await jwtVerify2(token, key, { algorithms: ["HS256"] });
6380
+ return payload;
6381
+ } catch {
6382
+ return null;
6383
+ }
6384
+ }
6385
+ function pickName(payload) {
6386
+ const metadataFullName = typeof payload.user_metadata?.full_name === "string" ? payload.user_metadata.full_name : null;
6387
+ const metadataName = typeof payload.user_metadata?.name === "string" ? payload.user_metadata.name : null;
6388
+ const claimName = typeof payload.name === "string" ? payload.name : null;
6389
+ return metadataFullName || metadataName || claimName || null;
6390
+ }
6391
+ function pickEmail(payload) {
6392
+ return typeof payload.email === "string" ? payload.email : null;
6393
+ }
6394
+ function unauthorized(details) {
6395
+ return { error: "Unauthorized", status: 401, details };
6396
+ }
6325
6397
  async function getAuthenticatedUserCore(request, options = {}) {
6326
6398
  try {
6327
- const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
6328
- const userIdOrError = requireUserId(request);
6329
- if (userIdOrError instanceof Response) {
6330
- const clonedResponse = userIdOrError.clone();
6331
- const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
6332
- return {
6333
- error: body.error || "Unauthorized",
6334
- status: userIdOrError.status,
6335
- details: body.error || "Unauthorized"
6336
- };
6399
+ const includeEmail = options.includeEmail !== false;
6400
+ const includeName = options.includeName !== false;
6401
+ const headerUserId = request.headers.get("x-user-id");
6402
+ if (headerUserId) {
6403
+ let email = null;
6404
+ let name = null;
6405
+ if (includeEmail || includeName) {
6406
+ const token2 = extractBearerToken2(request);
6407
+ if (token2) {
6408
+ const secret2 = getConfiguredSecret();
6409
+ const payload2 = secret2 ? await verifyHs256(token2, secret2) : isStrictMode() ? null : decodeJwtUnverified(token2);
6410
+ if (payload2) {
6411
+ if (includeEmail) email = pickEmail(payload2);
6412
+ if (includeName) name = pickName(payload2);
6413
+ }
6414
+ }
6415
+ }
6416
+ return { userId: headerUserId, email, name };
6417
+ }
6418
+ const token = extractBearerToken2(request);
6419
+ if (!token) {
6420
+ return unauthorized("User ID not found. Ensure middleware is configured.");
6421
+ }
6422
+ const secret = getConfiguredSecret();
6423
+ let payload = null;
6424
+ if (secret) {
6425
+ payload = await verifyHs256(token, secret);
6426
+ if (!payload) {
6427
+ return unauthorized("Invalid or expired authentication token");
6428
+ }
6429
+ } else if (isStrictMode()) {
6430
+ return unauthorized(
6431
+ "Strict auth mode is enabled but no JWT secret is configured. Set SOLVAPAY_JWT_SECRET or SUPABASE_JWT_SECRET."
6432
+ );
6433
+ } else {
6434
+ payload = decodeJwtUnverified(token);
6435
+ if (!payload) {
6436
+ return unauthorized("Malformed authentication token");
6437
+ }
6438
+ }
6439
+ const userId = typeof payload.sub === "string" ? payload.sub : null;
6440
+ if (!userId) {
6441
+ return unauthorized("Authentication token missing subject (sub) claim");
6337
6442
  }
6338
- const userId = userIdOrError;
6339
- const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
6340
- const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
6341
6443
  return {
6342
6444
  userId,
6343
- email,
6344
- name
6445
+ email: includeEmail ? pickEmail(payload) : null,
6446
+ name: includeName ? pickName(payload) : null
6345
6447
  };
6346
6448
  } catch (error) {
6347
6449
  return handleRouteError(error, "Get authenticated user", "Authentication failed");
package/dist/index.d.cts CHANGED
@@ -3812,8 +3812,16 @@ declare function handleRouteError(error: unknown, operationName: string, default
3812
3812
  * and name from authenticated requests. Works with any framework that uses
3813
3813
  * the standard Web API Request (Express, Fastify, Next.js, Edge Functions, etc.).
3814
3814
  *
3815
- * Uses dynamic imports to avoid requiring @solvapay/auth at build time,
3816
- * making it suitable for edge runtime environments.
3815
+ * Resolution order:
3816
+ * 1. `x-user-id` header set by Next.js-style middleware (unchanged).
3817
+ * 2. `Authorization: Bearer <jwt>` — verified via HS256 when a secret is
3818
+ * configured (`SOLVAPAY_JWT_SECRET` or `SUPABASE_JWT_SECRET`), or
3819
+ * unverified-decoded when no secret is configured. The unverified path
3820
+ * covers platforms that verify JWTs at the gateway (e.g. Supabase Edge
3821
+ * with `verify_jwt = true`, which is the default) and asymmetric signing
3822
+ * keys (ES256/RS256) that the SDK does not have keys for.
3823
+ * 3. Set `SOLVAPAY_AUTH_STRICT=true` to require cryptographic verification
3824
+ * inside the handler (the unverified-decode fallback is then disabled).
3817
3825
  *
3818
3826
  * @param request - Standard Web API Request object
3819
3827
  * @param options - Configuration options
package/dist/index.d.ts CHANGED
@@ -3812,8 +3812,16 @@ declare function handleRouteError(error: unknown, operationName: string, default
3812
3812
  * and name from authenticated requests. Works with any framework that uses
3813
3813
  * the standard Web API Request (Express, Fastify, Next.js, Edge Functions, etc.).
3814
3814
  *
3815
- * Uses dynamic imports to avoid requiring @solvapay/auth at build time,
3816
- * making it suitable for edge runtime environments.
3815
+ * Resolution order:
3816
+ * 1. `x-user-id` header set by Next.js-style middleware (unchanged).
3817
+ * 2. `Authorization: Bearer <jwt>` — verified via HS256 when a secret is
3818
+ * configured (`SOLVAPAY_JWT_SECRET` or `SUPABASE_JWT_SECRET`), or
3819
+ * unverified-decoded when no secret is configured. The unverified path
3820
+ * covers platforms that verify JWTs at the gateway (e.g. Supabase Edge
3821
+ * with `verify_jwt = true`, which is the default) and asymmetric signing
3822
+ * keys (ES256/RS256) that the SDK does not have keys for.
3823
+ * 3. Set `SOLVAPAY_AUTH_STRICT=true` to require cryptographic verification
3824
+ * inside the handler (the unverified-decode fallback is then disabled).
3817
3825
  *
3818
3826
  * @param request - Standard Web API Request object
3819
3827
  * @param options - Configuration options
package/dist/index.js CHANGED
@@ -1973,26 +1973,128 @@ function handleRouteError(error, operationName, defaultMessage) {
1973
1973
  }
1974
1974
 
1975
1975
  // src/helpers/auth.ts
1976
+ function base64UrlDecode2(input) {
1977
+ const padded = input.replace(/-/g, "+").replace(/_/g, "/");
1978
+ const padding = padded.length % 4 === 0 ? "" : "=".repeat(4 - padded.length % 4);
1979
+ const base64 = padded + padding;
1980
+ if (typeof atob === "function") {
1981
+ const binary = atob(base64);
1982
+ let result = "";
1983
+ for (let i = 0; i < binary.length; i++) {
1984
+ result += String.fromCharCode(binary.charCodeAt(i));
1985
+ }
1986
+ try {
1987
+ return decodeURIComponent(escape(result));
1988
+ } catch {
1989
+ return result;
1990
+ }
1991
+ }
1992
+ const BufferCtor = globalThis.Buffer;
1993
+ if (BufferCtor) {
1994
+ return BufferCtor.from(base64, "base64").toString("utf-8");
1995
+ }
1996
+ throw new Error("No base64 decoder available in this runtime");
1997
+ }
1998
+ function decodeJwtUnverified(token) {
1999
+ const parts = token.split(".");
2000
+ if (parts.length !== 3) return null;
2001
+ try {
2002
+ const json = base64UrlDecode2(parts[1]);
2003
+ const payload = JSON.parse(json);
2004
+ if (typeof payload !== "object" || payload === null) return null;
2005
+ return payload;
2006
+ } catch {
2007
+ return null;
2008
+ }
2009
+ }
2010
+ function extractBearerToken2(request) {
2011
+ const authHeader = request.headers.get("authorization");
2012
+ if (!authHeader || !authHeader.toLowerCase().startsWith("bearer ")) return null;
2013
+ const token = authHeader.slice(7).trim();
2014
+ return token.length > 0 ? token : null;
2015
+ }
2016
+ function readEnv(name) {
2017
+ const proc = globalThis.process;
2018
+ return proc?.env?.[name];
2019
+ }
2020
+ function isStrictMode() {
2021
+ return readEnv("SOLVAPAY_AUTH_STRICT") === "true";
2022
+ }
2023
+ function getConfiguredSecret() {
2024
+ return readEnv("SOLVAPAY_JWT_SECRET") || readEnv("SUPABASE_JWT_SECRET");
2025
+ }
2026
+ async function verifyHs256(token, secret) {
2027
+ try {
2028
+ const { jwtVerify } = await import("./webapi-K5XBCEO6.js");
2029
+ const key = new TextEncoder().encode(secret);
2030
+ const { payload } = await jwtVerify(token, key, { algorithms: ["HS256"] });
2031
+ return payload;
2032
+ } catch {
2033
+ return null;
2034
+ }
2035
+ }
2036
+ function pickName(payload) {
2037
+ const metadataFullName = typeof payload.user_metadata?.full_name === "string" ? payload.user_metadata.full_name : null;
2038
+ const metadataName = typeof payload.user_metadata?.name === "string" ? payload.user_metadata.name : null;
2039
+ const claimName = typeof payload.name === "string" ? payload.name : null;
2040
+ return metadataFullName || metadataName || claimName || null;
2041
+ }
2042
+ function pickEmail(payload) {
2043
+ return typeof payload.email === "string" ? payload.email : null;
2044
+ }
2045
+ function unauthorized(details) {
2046
+ return { error: "Unauthorized", status: 401, details };
2047
+ }
1976
2048
  async function getAuthenticatedUserCore(request, options = {}) {
1977
2049
  try {
1978
- const { requireUserId, getUserEmailFromRequest, getUserNameFromRequest } = await import("@solvapay/auth");
1979
- const userIdOrError = requireUserId(request);
1980
- if (userIdOrError instanceof Response) {
1981
- const clonedResponse = userIdOrError.clone();
1982
- const body = await clonedResponse.json().catch(() => ({ error: "Unauthorized" }));
1983
- return {
1984
- error: body.error || "Unauthorized",
1985
- status: userIdOrError.status,
1986
- details: body.error || "Unauthorized"
1987
- };
2050
+ const includeEmail = options.includeEmail !== false;
2051
+ const includeName = options.includeName !== false;
2052
+ const headerUserId = request.headers.get("x-user-id");
2053
+ if (headerUserId) {
2054
+ let email = null;
2055
+ let name = null;
2056
+ if (includeEmail || includeName) {
2057
+ const token2 = extractBearerToken2(request);
2058
+ if (token2) {
2059
+ const secret2 = getConfiguredSecret();
2060
+ const payload2 = secret2 ? await verifyHs256(token2, secret2) : isStrictMode() ? null : decodeJwtUnverified(token2);
2061
+ if (payload2) {
2062
+ if (includeEmail) email = pickEmail(payload2);
2063
+ if (includeName) name = pickName(payload2);
2064
+ }
2065
+ }
2066
+ }
2067
+ return { userId: headerUserId, email, name };
2068
+ }
2069
+ const token = extractBearerToken2(request);
2070
+ if (!token) {
2071
+ return unauthorized("User ID not found. Ensure middleware is configured.");
2072
+ }
2073
+ const secret = getConfiguredSecret();
2074
+ let payload = null;
2075
+ if (secret) {
2076
+ payload = await verifyHs256(token, secret);
2077
+ if (!payload) {
2078
+ return unauthorized("Invalid or expired authentication token");
2079
+ }
2080
+ } else if (isStrictMode()) {
2081
+ return unauthorized(
2082
+ "Strict auth mode is enabled but no JWT secret is configured. Set SOLVAPAY_JWT_SECRET or SUPABASE_JWT_SECRET."
2083
+ );
2084
+ } else {
2085
+ payload = decodeJwtUnverified(token);
2086
+ if (!payload) {
2087
+ return unauthorized("Malformed authentication token");
2088
+ }
2089
+ }
2090
+ const userId = typeof payload.sub === "string" ? payload.sub : null;
2091
+ if (!userId) {
2092
+ return unauthorized("Authentication token missing subject (sub) claim");
1988
2093
  }
1989
- const userId = userIdOrError;
1990
- const email = options.includeEmail !== false ? await getUserEmailFromRequest(request) : null;
1991
- const name = options.includeName !== false ? await getUserNameFromRequest(request) : null;
1992
2094
  return {
1993
2095
  userId,
1994
- email,
1995
- name
2096
+ email: includeEmail ? pickEmail(payload) : null,
2097
+ name: includeName ? pickName(payload) : null
1996
2098
  };
1997
2099
  } catch (error) {
1998
2100
  return handleRouteError(error, "Get authenticated user", "Authentication failed");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solvapay/server",
3
- "version": "1.0.8-preview.1",
3
+ "version": "1.0.8-preview.11",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",
@@ -37,12 +37,12 @@
37
37
  },
38
38
  "sideEffects": false,
39
39
  "dependencies": {
40
- "@solvapay/core": "1.0.8-preview.1"
40
+ "@solvapay/core": "1.0.8-preview.11"
41
41
  },
42
42
  "peerDependencies": {
43
43
  "@modelcontextprotocol/sdk": "^1.28.0",
44
44
  "zod": "^3.25.0 || ^4.0.0",
45
- "@solvapay/auth": "1.0.8-preview.1"
45
+ "@solvapay/auth": "1.0.8-preview.11"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@modelcontextprotocol/sdk": {
@@ -60,15 +60,15 @@
60
60
  "tsx": "^4.21.0",
61
61
  "typescript": "^5.9.3",
62
62
  "vitest": "^4.1.2",
63
- "@solvapay/auth": "1.0.8-preview.1",
64
63
  "@solvapay/demo-services": "0.0.0",
65
- "@solvapay/test-utils": "^0.0.0"
64
+ "@solvapay/test-utils": "^0.0.0",
65
+ "@solvapay/auth": "1.0.8-preview.11"
66
66
  },
67
67
  "scripts": {
68
68
  "build": "tsup src/index.ts --format esm,cjs --dts --tsconfig tsconfig.build.json && tsup src/edge.ts --format esm --dts --tsconfig tsconfig.build.json",
69
69
  "generate:types": "tsx scripts/generate-types.ts",
70
- "test": "vitest run __tests__/paywall.unit.test.ts __tests__/mcp-auth.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts",
71
- "test:unit": "vitest run __tests__/paywall.unit.test.ts __tests__/mcp-auth.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts",
70
+ "test": "vitest run __tests__/paywall.unit.test.ts __tests__/mcp-auth.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts __tests__/edge-exports.unit.test.ts __tests__/auth-core.unit.test.ts",
71
+ "test:unit": "vitest run __tests__/paywall.unit.test.ts __tests__/mcp-auth.unit.test.ts __tests__/bootstrap-mcp.unit.test.ts __tests__/verify-webhook.unit.test.ts __tests__/edge-exports.unit.test.ts __tests__/auth-core.unit.test.ts",
72
72
  "test:integration": "vitest run __tests__/backend.integration.test.ts",
73
73
  "test:integration:payment": "vitest run __tests__/payment-stripe.integration.test.ts",
74
74
  "test:all": "vitest run",