@solvapay/server 1.0.8-preview.1 → 1.0.8-preview.10
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 +34 -3
- package/dist/edge.js +189 -15
- package/dist/index.cjs +117 -15
- package/dist/index.d.cts +10 -2
- package/dist/index.d.ts +10 -2
- package/dist/index.js +117 -15
- package/package.json +6 -6
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
|
-
*
|
|
3724
|
-
*
|
|
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
|
|
1764
|
-
const
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
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
|
|
6328
|
-
const
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
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
|
-
*
|
|
3816
|
-
*
|
|
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
|
-
*
|
|
3816
|
-
*
|
|
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
|
|
1979
|
-
const
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
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.
|
|
3
|
+
"version": "1.0.8-preview.10",
|
|
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.
|
|
40
|
+
"@solvapay/core": "1.0.8-preview.10"
|
|
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.
|
|
45
|
+
"@solvapay/auth": "1.0.8-preview.10"
|
|
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.
|
|
63
|
+
"@solvapay/auth": "1.0.8-preview.10",
|
|
64
64
|
"@solvapay/demo-services": "0.0.0",
|
|
65
65
|
"@solvapay/test-utils": "^0.0.0"
|
|
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",
|