@scalemule/nextjs 0.0.39 → 0.0.41
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/client.d.mts +5 -1
- package/dist/client.d.ts +5 -1
- package/dist/{index-BoENfro3.d.mts → index-BIIUrnPr.d.mts} +54 -1
- package/dist/{index-BoENfro3.d.ts → index-BIIUrnPr.d.ts} +54 -1
- package/dist/index.d.mts +15 -4
- package/dist/index.d.ts +15 -4
- package/dist/index.js +176 -89
- package/dist/index.mjs +168 -90
- package/dist/server/auth.js +275 -6
- package/dist/server/auth.mjs +275 -6
- package/dist/server/index.d.mts +252 -4
- package/dist/server/index.d.ts +252 -4
- package/dist/server/index.js +403 -6
- package/dist/server/index.mjs +393 -7
- package/dist/server/webhook-handler.d.mts +3 -2
- package/dist/server/webhook-handler.d.ts +3 -2
- package/dist/server/webhook-handler.js +3 -0
- package/dist/server/webhook-handler.mjs +3 -0
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{webhook-handler-CjSSCdvI.d.ts → webhook-handler-BjFbqZuq.d.ts} +4 -1
- package/dist/{webhook-handler-Cz9jtet2.d.mts → webhook-handler-BqzCYRNJ.d.mts} +4 -1
- package/package.json +3 -2
package/dist/server/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createMoneyClient } from '@scalemule/money';
|
|
1
2
|
import { cookies, headers } from 'next/headers';
|
|
2
3
|
import { NextResponse } from 'next/server';
|
|
3
4
|
import { createHmac, timingSafeEqual, createHash } from 'crypto';
|
|
@@ -2665,6 +2666,14 @@ var ScaleMuleServer = class {
|
|
|
2665
2666
|
this.apiKey = config.apiKey;
|
|
2666
2667
|
this.gatewayUrl = resolveGatewayUrl(config);
|
|
2667
2668
|
this.debug = config.debug || false;
|
|
2669
|
+
this.money = createMoneyClient({
|
|
2670
|
+
apiKey: this.apiKey,
|
|
2671
|
+
gatewayUrl: this.gatewayUrl,
|
|
2672
|
+
fetch: globalThis.fetch.bind(globalThis)
|
|
2673
|
+
});
|
|
2674
|
+
}
|
|
2675
|
+
moneyWithSession(sessionToken) {
|
|
2676
|
+
return this.money.withAccessToken(sessionToken);
|
|
2668
2677
|
}
|
|
2669
2678
|
/**
|
|
2670
2679
|
* Make a request to the ScaleMule API
|
|
@@ -2743,6 +2752,7 @@ function createServerClient(config) {
|
|
|
2743
2752
|
}
|
|
2744
2753
|
var SESSION_COOKIE_NAME = "sm_session";
|
|
2745
2754
|
var USER_ID_COOKIE_NAME = "sm_user_id";
|
|
2755
|
+
var KNOWN_ACCOUNTS_COOKIE_NAME = "sm_known_accounts";
|
|
2746
2756
|
({
|
|
2747
2757
|
secure: process.env.NODE_ENV === "production"});
|
|
2748
2758
|
function createCookieHeader(name, value, options = {}) {
|
|
@@ -2843,6 +2853,163 @@ function getSessionFromRequest(request) {
|
|
|
2843
2853
|
expiresAt: /* @__PURE__ */ new Date()
|
|
2844
2854
|
};
|
|
2845
2855
|
}
|
|
2856
|
+
var MAX_KNOWN_ACCOUNTS = 10;
|
|
2857
|
+
function maskEmail(email) {
|
|
2858
|
+
const [local, domain] = email.split("@");
|
|
2859
|
+
if (!domain) return "***@***.***";
|
|
2860
|
+
const tldDot = domain.lastIndexOf(".");
|
|
2861
|
+
const tld = tldDot > 0 ? domain.slice(tldDot) : "";
|
|
2862
|
+
const domainBase = tldDot > 0 ? domain.slice(0, tldDot) : domain;
|
|
2863
|
+
return `${local[0] || "*"}***@${domainBase[0] || "*"}***${tld}`;
|
|
2864
|
+
}
|
|
2865
|
+
function stableColorIndex(userId) {
|
|
2866
|
+
let hash = 0;
|
|
2867
|
+
for (let i = 0; i < userId.length; i++) {
|
|
2868
|
+
hash = (hash << 5) - hash + userId.charCodeAt(i) | 0;
|
|
2869
|
+
}
|
|
2870
|
+
return Math.abs(hash) % 8;
|
|
2871
|
+
}
|
|
2872
|
+
function applyPrivacyToEntry(entry, privacy) {
|
|
2873
|
+
switch (privacy) {
|
|
2874
|
+
case "full":
|
|
2875
|
+
return entry;
|
|
2876
|
+
case "masked":
|
|
2877
|
+
return {
|
|
2878
|
+
userId: entry.userId,
|
|
2879
|
+
email: entry.email ? maskEmail(entry.email) : void 0,
|
|
2880
|
+
fullName: entry.fullName ? `${entry.fullName[0].toUpperCase()}.` : void 0,
|
|
2881
|
+
provider: entry.provider,
|
|
2882
|
+
lastActiveAt: entry.lastActiveAt,
|
|
2883
|
+
colorIndex: stableColorIndex(entry.userId)
|
|
2884
|
+
};
|
|
2885
|
+
case "minimal":
|
|
2886
|
+
return {
|
|
2887
|
+
userId: entry.userId,
|
|
2888
|
+
provider: entry.provider,
|
|
2889
|
+
lastActiveAt: entry.lastActiveAt,
|
|
2890
|
+
displayLabel: "Account",
|
|
2891
|
+
colorIndex: stableColorIndex(entry.userId)
|
|
2892
|
+
};
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
function appendKnownAccountCookie(headers2, account, existingCookie, options = {}, privacy) {
|
|
2896
|
+
let accounts = {};
|
|
2897
|
+
if (existingCookie) {
|
|
2898
|
+
try {
|
|
2899
|
+
accounts = JSON.parse(decodeURIComponent(existingCookie));
|
|
2900
|
+
} catch {
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
const effectivePrivacy = privacy || "full";
|
|
2904
|
+
for (const [userId, entry] of Object.entries(accounts)) {
|
|
2905
|
+
accounts[userId] = applyPrivacyToEntry(entry, effectivePrivacy);
|
|
2906
|
+
}
|
|
2907
|
+
accounts[account.userId] = applyPrivacyToEntry(account, effectivePrivacy);
|
|
2908
|
+
const entries = Object.entries(accounts);
|
|
2909
|
+
if (entries.length > MAX_KNOWN_ACCOUNTS) {
|
|
2910
|
+
entries.sort((a, b) => new Date(b[1].lastActiveAt).getTime() - new Date(a[1].lastActiveAt).getTime());
|
|
2911
|
+
accounts = Object.fromEntries(entries.slice(0, MAX_KNOWN_ACCOUNTS));
|
|
2912
|
+
}
|
|
2913
|
+
const maxAge = 365 * 24 * 60 * 60;
|
|
2914
|
+
const secure = options.secure ?? process.env.NODE_ENV === "production";
|
|
2915
|
+
const sameSite = options.sameSite ?? "lax";
|
|
2916
|
+
const path = options.path ?? "/";
|
|
2917
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(accounts))}; Path=${path}; Max-Age=${maxAge}; SameSite=${sameSite}`;
|
|
2918
|
+
if (secure) {
|
|
2919
|
+
cookie += "; Secure";
|
|
2920
|
+
}
|
|
2921
|
+
if (options.domain) {
|
|
2922
|
+
cookie += `; Domain=${options.domain}`;
|
|
2923
|
+
}
|
|
2924
|
+
headers2.append("Set-Cookie", cookie);
|
|
2925
|
+
}
|
|
2926
|
+
function removeKnownAccountFromCookie(headers2, userId, existingCookie, options = {}) {
|
|
2927
|
+
let accounts = {};
|
|
2928
|
+
if (existingCookie) {
|
|
2929
|
+
try {
|
|
2930
|
+
accounts = JSON.parse(decodeURIComponent(existingCookie));
|
|
2931
|
+
} catch {
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
delete accounts[userId];
|
|
2935
|
+
const maxAge = 365 * 24 * 60 * 60;
|
|
2936
|
+
const secure = options.secure ?? process.env.NODE_ENV === "production";
|
|
2937
|
+
const sameSite = options.sameSite ?? "lax";
|
|
2938
|
+
const path = options.path ?? "/";
|
|
2939
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(accounts))}; Path=${path}; Max-Age=${maxAge}; SameSite=${sameSite}`;
|
|
2940
|
+
if (secure) {
|
|
2941
|
+
cookie += "; Secure";
|
|
2942
|
+
}
|
|
2943
|
+
if (options.domain) {
|
|
2944
|
+
cookie += `; Domain=${options.domain}`;
|
|
2945
|
+
}
|
|
2946
|
+
headers2.append("Set-Cookie", cookie);
|
|
2947
|
+
}
|
|
2948
|
+
function clearKnownAccountsCookie(headers2, options = {}) {
|
|
2949
|
+
const path = options.path ?? "/";
|
|
2950
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=; Path=${path}; Max-Age=0`;
|
|
2951
|
+
if (options.domain) {
|
|
2952
|
+
cookie += `; Domain=${options.domain}`;
|
|
2953
|
+
}
|
|
2954
|
+
headers2.append("Set-Cookie", cookie);
|
|
2955
|
+
}
|
|
2956
|
+
function getKnownAccountsFromRequest(request) {
|
|
2957
|
+
const cookieHeader = request.headers.get("cookie");
|
|
2958
|
+
if (!cookieHeader) return [];
|
|
2959
|
+
const cookies4 = Object.fromEntries(
|
|
2960
|
+
cookieHeader.split(";").map((c) => {
|
|
2961
|
+
const [key, ...rest] = c.trim().split("=");
|
|
2962
|
+
return [key, decodeURIComponent(rest.join("="))];
|
|
2963
|
+
})
|
|
2964
|
+
);
|
|
2965
|
+
const raw = cookies4[KNOWN_ACCOUNTS_COOKIE_NAME];
|
|
2966
|
+
if (!raw) return [];
|
|
2967
|
+
try {
|
|
2968
|
+
const accounts = JSON.parse(raw);
|
|
2969
|
+
return Object.values(accounts);
|
|
2970
|
+
} catch {
|
|
2971
|
+
return [];
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
function getKnownAccountsCookieRaw(request) {
|
|
2975
|
+
const cookieHeader = request.headers.get("cookie");
|
|
2976
|
+
if (!cookieHeader) return null;
|
|
2977
|
+
const cookies4 = Object.fromEntries(
|
|
2978
|
+
cookieHeader.split(";").map((c) => {
|
|
2979
|
+
const [key, ...rest] = c.trim().split("=");
|
|
2980
|
+
return [key, decodeURIComponent(rest.join("="))];
|
|
2981
|
+
})
|
|
2982
|
+
);
|
|
2983
|
+
return cookies4[KNOWN_ACCOUNTS_COOKIE_NAME] || null;
|
|
2984
|
+
}
|
|
2985
|
+
function normalizeKnownAccountsCookie(request, privacy, options = {}) {
|
|
2986
|
+
if (!privacy || privacy === "full") return null;
|
|
2987
|
+
const raw = getKnownAccountsCookieRaw(request);
|
|
2988
|
+
if (!raw) return null;
|
|
2989
|
+
let accounts = {};
|
|
2990
|
+
try {
|
|
2991
|
+
accounts = JSON.parse(raw);
|
|
2992
|
+
} catch {
|
|
2993
|
+
return null;
|
|
2994
|
+
}
|
|
2995
|
+
let changed = false;
|
|
2996
|
+
for (const [userId, entry] of Object.entries(accounts)) {
|
|
2997
|
+
const normalized = applyPrivacyToEntry(entry, privacy);
|
|
2998
|
+
if (JSON.stringify(normalized) !== JSON.stringify(entry)) {
|
|
2999
|
+
accounts[userId] = normalized;
|
|
3000
|
+
changed = true;
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
if (!changed) return null;
|
|
3004
|
+
const maxAge = 365 * 24 * 60 * 60;
|
|
3005
|
+
const secure = options.secure ?? process.env.NODE_ENV === "production";
|
|
3006
|
+
const sameSite = options.sameSite ?? "lax";
|
|
3007
|
+
const path = options.path ?? "/";
|
|
3008
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(accounts))}; Path=${path}; Max-Age=${maxAge}; SameSite=${sameSite}`;
|
|
3009
|
+
if (secure) cookie += "; Secure";
|
|
3010
|
+
if (options.domain) cookie += `; Domain=${options.domain}`;
|
|
3011
|
+
return cookie;
|
|
3012
|
+
}
|
|
2846
3013
|
async function requireSession() {
|
|
2847
3014
|
const session = await getSession();
|
|
2848
3015
|
if (!session) {
|
|
@@ -3002,7 +3169,25 @@ function createAuthRoutes(config = {}) {
|
|
|
3002
3169
|
} catch {
|
|
3003
3170
|
return successResponse({ user: registeredUser, message: "Registration successful" }, 201);
|
|
3004
3171
|
}
|
|
3005
|
-
|
|
3172
|
+
const registerResponse = withSession(loginData, { user: registeredUser, sessionToken: loginData.session_token, userId: registeredUser.id }, cookieOptions);
|
|
3173
|
+
if (config.enableAccountSwitcher) {
|
|
3174
|
+
const existingKnown = getKnownAccountsCookieRaw(request);
|
|
3175
|
+
appendKnownAccountCookie(
|
|
3176
|
+
registerResponse.headers,
|
|
3177
|
+
{
|
|
3178
|
+
userId: registeredUser.id,
|
|
3179
|
+
email: registeredUser.email,
|
|
3180
|
+
fullName: registeredUser.full_name ?? void 0,
|
|
3181
|
+
avatarUrl: registeredUser.avatar_url ?? void 0,
|
|
3182
|
+
provider: "email",
|
|
3183
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3184
|
+
},
|
|
3185
|
+
existingKnown,
|
|
3186
|
+
cookieOptions,
|
|
3187
|
+
config.accountSwitcherPrivacy
|
|
3188
|
+
);
|
|
3189
|
+
}
|
|
3190
|
+
return registerResponse;
|
|
3006
3191
|
}
|
|
3007
3192
|
// ==================== Login ====================
|
|
3008
3193
|
case "login": {
|
|
@@ -3033,7 +3218,25 @@ function createAuthRoutes(config = {}) {
|
|
|
3033
3218
|
email: loginData.user.email
|
|
3034
3219
|
});
|
|
3035
3220
|
}
|
|
3036
|
-
|
|
3221
|
+
const loginResponse = withSession(loginData, { user: loginData.user, sessionToken: loginData.session_token, userId: loginData.user.id }, cookieOptions);
|
|
3222
|
+
if (config.enableAccountSwitcher) {
|
|
3223
|
+
const existingKnown = getKnownAccountsCookieRaw(request);
|
|
3224
|
+
appendKnownAccountCookie(
|
|
3225
|
+
loginResponse.headers,
|
|
3226
|
+
{
|
|
3227
|
+
userId: loginData.user.id,
|
|
3228
|
+
email: loginData.user.email,
|
|
3229
|
+
fullName: loginData.user.full_name ?? void 0,
|
|
3230
|
+
avatarUrl: loginData.user.avatar_url ?? void 0,
|
|
3231
|
+
provider: "email",
|
|
3232
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3233
|
+
},
|
|
3234
|
+
existingKnown,
|
|
3235
|
+
cookieOptions,
|
|
3236
|
+
config.accountSwitcherPrivacy
|
|
3237
|
+
);
|
|
3238
|
+
}
|
|
3239
|
+
return loginResponse;
|
|
3037
3240
|
}
|
|
3038
3241
|
// ==================== Logout ====================
|
|
3039
3242
|
case "logout": {
|
|
@@ -3180,6 +3383,59 @@ function createAuthRoutes(config = {}) {
|
|
|
3180
3383
|
}
|
|
3181
3384
|
return successResponse({ message: "Password changed successfully" });
|
|
3182
3385
|
}
|
|
3386
|
+
// ==================== Switch Account ====================
|
|
3387
|
+
// Clears the active session so the user can re-authenticate as a different account.
|
|
3388
|
+
// The known accounts cookie is preserved — only the session cookie is cleared.
|
|
3389
|
+
case "switch-account": {
|
|
3390
|
+
if (!config.enableAccountSwitcher) {
|
|
3391
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3392
|
+
}
|
|
3393
|
+
const session = await getSession();
|
|
3394
|
+
if (session) {
|
|
3395
|
+
try {
|
|
3396
|
+
await sm.auth.logout(session.sessionToken);
|
|
3397
|
+
} catch {
|
|
3398
|
+
}
|
|
3399
|
+
}
|
|
3400
|
+
const switchResponse = clearSession({ message: "Session cleared for account switch" }, cookieOptions);
|
|
3401
|
+
const knownAccounts = getKnownAccountsFromRequest(request);
|
|
3402
|
+
return new Response(
|
|
3403
|
+
JSON.stringify({ success: true, data: { message: "Session cleared for account switch", knownAccounts } }),
|
|
3404
|
+
{ status: 200, headers: switchResponse.headers }
|
|
3405
|
+
);
|
|
3406
|
+
}
|
|
3407
|
+
// ==================== Forget Account ====================
|
|
3408
|
+
// Remove a specific account from the known accounts cookie.
|
|
3409
|
+
case "forget-account": {
|
|
3410
|
+
if (!config.enableAccountSwitcher) {
|
|
3411
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3412
|
+
}
|
|
3413
|
+
const { user_id } = body;
|
|
3414
|
+
if (!user_id) {
|
|
3415
|
+
return errorResponse("VALIDATION_ERROR", "user_id required", 400);
|
|
3416
|
+
}
|
|
3417
|
+
const headers2 = new Headers();
|
|
3418
|
+
headers2.set("Content-Type", "application/json");
|
|
3419
|
+
const existingKnown = getKnownAccountsCookieRaw(request);
|
|
3420
|
+
removeKnownAccountFromCookie(headers2, user_id, existingKnown, cookieOptions);
|
|
3421
|
+
return new Response(
|
|
3422
|
+
JSON.stringify({ success: true, data: { message: "Account forgotten" } }),
|
|
3423
|
+
{ status: 200, headers: headers2 }
|
|
3424
|
+
);
|
|
3425
|
+
}
|
|
3426
|
+
// ==================== Forget All Accounts ====================
|
|
3427
|
+
case "forget-all-accounts": {
|
|
3428
|
+
if (!config.enableAccountSwitcher) {
|
|
3429
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3430
|
+
}
|
|
3431
|
+
const headers2 = new Headers();
|
|
3432
|
+
headers2.set("Content-Type", "application/json");
|
|
3433
|
+
clearKnownAccountsCookie(headers2, cookieOptions);
|
|
3434
|
+
return new Response(
|
|
3435
|
+
JSON.stringify({ success: true, data: { message: "All accounts forgotten" } }),
|
|
3436
|
+
{ status: 200, headers: headers2 }
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3183
3439
|
default:
|
|
3184
3440
|
return errorResponse("NOT_FOUND", `Unknown endpoint: ${path}`, 404);
|
|
3185
3441
|
}
|
|
@@ -3195,20 +3451,25 @@ function createAuthRoutes(config = {}) {
|
|
|
3195
3451
|
switch (path) {
|
|
3196
3452
|
// ==================== Get Current User ====================
|
|
3197
3453
|
case "me": {
|
|
3454
|
+
const normCookie = config.enableAccountSwitcher ? normalizeKnownAccountsCookie(request, config.accountSwitcherPrivacy, cookieOptions) : null;
|
|
3455
|
+
const withNorm = (resp) => {
|
|
3456
|
+
if (normCookie) resp.headers.append("Set-Cookie", normCookie);
|
|
3457
|
+
return resp;
|
|
3458
|
+
};
|
|
3198
3459
|
const session = await getSession();
|
|
3199
3460
|
if (!session) {
|
|
3200
|
-
return errorResponse("UNAUTHORIZED", "Authentication required", 401);
|
|
3461
|
+
return withNorm(errorResponse("UNAUTHORIZED", "Authentication required", 401));
|
|
3201
3462
|
}
|
|
3202
3463
|
let userData;
|
|
3203
3464
|
try {
|
|
3204
3465
|
userData = await sm.auth.me(session.sessionToken);
|
|
3205
3466
|
} catch {
|
|
3206
|
-
return clearSession(
|
|
3467
|
+
return withNorm(clearSession(
|
|
3207
3468
|
{ error: { code: "SESSION_EXPIRED", message: "Session expired" } },
|
|
3208
3469
|
cookieOptions
|
|
3209
|
-
);
|
|
3470
|
+
));
|
|
3210
3471
|
}
|
|
3211
|
-
return successResponse({ user: userData, sessionToken: session.sessionToken, userId: session.userId });
|
|
3472
|
+
return withNorm(successResponse({ user: userData, sessionToken: session.sessionToken, userId: session.userId }));
|
|
3212
3473
|
}
|
|
3213
3474
|
// ==================== Get Session Status ====================
|
|
3214
3475
|
case "session": {
|
|
@@ -3218,6 +3479,14 @@ function createAuthRoutes(config = {}) {
|
|
|
3218
3479
|
userId: session?.userId || null
|
|
3219
3480
|
});
|
|
3220
3481
|
}
|
|
3482
|
+
// ==================== Get Known Accounts ====================
|
|
3483
|
+
case "known-accounts": {
|
|
3484
|
+
if (!config.enableAccountSwitcher) {
|
|
3485
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3486
|
+
}
|
|
3487
|
+
const knownAccounts = getKnownAccountsFromRequest(request);
|
|
3488
|
+
return successResponse({ knownAccounts });
|
|
3489
|
+
}
|
|
3221
3490
|
default:
|
|
3222
3491
|
return errorResponse("NOT_FOUND", `Unknown endpoint: ${path}`, 404);
|
|
3223
3492
|
}
|
|
@@ -4825,4 +5094,121 @@ async function prefetchBundles(keys) {
|
|
|
4825
5094
|
await Promise.all(keys.map((key) => getBundle(key)));
|
|
4826
5095
|
}
|
|
4827
5096
|
|
|
4828
|
-
|
|
5097
|
+
// src/server/redirect.ts
|
|
5098
|
+
function normalizeOrigin(origin) {
|
|
5099
|
+
try {
|
|
5100
|
+
const u = new URL(origin);
|
|
5101
|
+
return `${u.protocol}//${u.host}`.toLowerCase();
|
|
5102
|
+
} catch {
|
|
5103
|
+
return null;
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
function isSafeSchemeRelative(input) {
|
|
5107
|
+
return !(input.startsWith("//") || input.startsWith("\\"));
|
|
5108
|
+
}
|
|
5109
|
+
function validateSafeRedirect(input, opts = {}) {
|
|
5110
|
+
const defaultPath = opts.defaultPath ?? "/";
|
|
5111
|
+
if (!input || typeof input !== "string") return defaultPath;
|
|
5112
|
+
const trimmed = input.trim();
|
|
5113
|
+
if (!trimmed) return defaultPath;
|
|
5114
|
+
if (!isSafeSchemeRelative(trimmed)) return defaultPath;
|
|
5115
|
+
const colon = trimmed.indexOf(":");
|
|
5116
|
+
if (colon > 0 && colon < 15) {
|
|
5117
|
+
const scheme = trimmed.slice(0, colon).toLowerCase();
|
|
5118
|
+
if (/^[a-z][a-z0-9+.-]*$/.test(scheme) && scheme !== "http" && scheme !== "https") {
|
|
5119
|
+
return defaultPath;
|
|
5120
|
+
}
|
|
5121
|
+
}
|
|
5122
|
+
if (trimmed.startsWith("/")) {
|
|
5123
|
+
if (trimmed.length > 1 && trimmed[1] === "\\") return defaultPath;
|
|
5124
|
+
return trimmed;
|
|
5125
|
+
}
|
|
5126
|
+
const allowed = (opts.allowedOrigins ?? []).map(normalizeOrigin).filter((o) => o !== null);
|
|
5127
|
+
try {
|
|
5128
|
+
const parsed = new URL(trimmed);
|
|
5129
|
+
const origin = `${parsed.protocol}//${parsed.host}`.toLowerCase();
|
|
5130
|
+
if (!allowed.includes(origin)) return defaultPath;
|
|
5131
|
+
if (opts.stripSameOriginHost !== false) {
|
|
5132
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}` || "/";
|
|
5133
|
+
}
|
|
5134
|
+
return trimmed;
|
|
5135
|
+
} catch {
|
|
5136
|
+
return defaultPath;
|
|
5137
|
+
}
|
|
5138
|
+
}
|
|
5139
|
+
function isSafeRedirect(input, opts = {}) {
|
|
5140
|
+
if (!input || typeof input !== "string") return false;
|
|
5141
|
+
const defaultPath = `__unsafe_sentinel_${Math.random()}`;
|
|
5142
|
+
const resolved = validateSafeRedirect(input, { ...opts, defaultPath });
|
|
5143
|
+
return resolved !== defaultPath;
|
|
5144
|
+
}
|
|
5145
|
+
|
|
5146
|
+
// src/server/security-headers.ts
|
|
5147
|
+
var DEFAULT_PERMISSIONS_POLICY = {
|
|
5148
|
+
// Conference track features — allow self-origin only.
|
|
5149
|
+
camera: "self",
|
|
5150
|
+
microphone: "self",
|
|
5151
|
+
"display-capture": "self",
|
|
5152
|
+
// Deny-by-default for features the SDK never uses.
|
|
5153
|
+
geolocation: "()",
|
|
5154
|
+
payment: "()",
|
|
5155
|
+
"publickey-credentials-get": "self",
|
|
5156
|
+
usb: "()",
|
|
5157
|
+
serial: "()",
|
|
5158
|
+
bluetooth: "()"
|
|
5159
|
+
};
|
|
5160
|
+
function formatPermissionsDirective(feature, value) {
|
|
5161
|
+
if (value === "*") return `${feature}=*`;
|
|
5162
|
+
if (value === "()" || Array.isArray(value) && value.length === 0) {
|
|
5163
|
+
return `${feature}=()`;
|
|
5164
|
+
}
|
|
5165
|
+
if (value === "self") return `${feature}=(self)`;
|
|
5166
|
+
const origins = value;
|
|
5167
|
+
const parts = origins.map((o) => o === "self" ? "self" : `"${o}"`);
|
|
5168
|
+
return `${feature}=(${parts.join(" ")})`;
|
|
5169
|
+
}
|
|
5170
|
+
function buildSecurityHeaders(opts = {}) {
|
|
5171
|
+
const headers2 = [];
|
|
5172
|
+
const includeHsts = opts.includeHsts ?? true;
|
|
5173
|
+
const hstsMaxAge = opts.hstsMaxAgeSeconds ?? 31536e3;
|
|
5174
|
+
if (includeHsts && hstsMaxAge > 0) {
|
|
5175
|
+
const parts = [`max-age=${hstsMaxAge}`];
|
|
5176
|
+
if (opts.hstsIncludeSubDomains ?? true) parts.push("includeSubDomains");
|
|
5177
|
+
if (opts.hstsPreload ?? false) parts.push("preload");
|
|
5178
|
+
headers2.push({
|
|
5179
|
+
key: "Strict-Transport-Security",
|
|
5180
|
+
value: parts.join("; ")
|
|
5181
|
+
});
|
|
5182
|
+
}
|
|
5183
|
+
const frameOptions = opts.frameOptions === void 0 ? "DENY" : opts.frameOptions;
|
|
5184
|
+
if (frameOptions !== null) {
|
|
5185
|
+
headers2.push({ key: "X-Frame-Options", value: frameOptions });
|
|
5186
|
+
}
|
|
5187
|
+
const contentTypeOptions = opts.contentTypeOptions === void 0 ? "nosniff" : opts.contentTypeOptions;
|
|
5188
|
+
if (contentTypeOptions !== null) {
|
|
5189
|
+
headers2.push({ key: "X-Content-Type-Options", value: contentTypeOptions });
|
|
5190
|
+
}
|
|
5191
|
+
headers2.push({
|
|
5192
|
+
key: "Referrer-Policy",
|
|
5193
|
+
value: opts.referrerPolicy ?? "strict-origin-when-cross-origin"
|
|
5194
|
+
});
|
|
5195
|
+
const xss = opts.xssProtection === void 0 ? "0" : opts.xssProtection;
|
|
5196
|
+
if (xss !== null) {
|
|
5197
|
+
headers2.push({ key: "X-XSS-Protection", value: xss });
|
|
5198
|
+
}
|
|
5199
|
+
const permissions = opts.permissionsPolicy === void 0 ? DEFAULT_PERMISSIONS_POLICY : opts.permissionsPolicy;
|
|
5200
|
+
if (permissions !== null) {
|
|
5201
|
+
const directives = Object.entries(permissions).map(([feature, value]) => formatPermissionsDirective(feature, value)).join(", ");
|
|
5202
|
+
headers2.push({ key: "Permissions-Policy", value: directives });
|
|
5203
|
+
}
|
|
5204
|
+
if (opts.extraHeaders?.length) {
|
|
5205
|
+
const byKey = new Map(headers2.map((h) => [h.key.toLowerCase(), h]));
|
|
5206
|
+
for (const extra of opts.extraHeaders) {
|
|
5207
|
+
byKey.set(extra.key.toLowerCase(), extra);
|
|
5208
|
+
}
|
|
5209
|
+
return Array.from(byKey.values());
|
|
5210
|
+
}
|
|
5211
|
+
return headers2;
|
|
5212
|
+
}
|
|
5213
|
+
|
|
5214
|
+
export { CSRF_COOKIE_NAME, CSRF_HEADER_NAME, DEFAULT_PERMISSIONS_POLICY, KNOWN_ACCOUNTS_COOKIE_NAME, OAUTH_STATE_COOKIE_NAME, SESSION_COOKIE_NAME, ScaleMuleError, ScaleMuleServer, USER_ID_COOKIE_NAME, apiHandler, appendKnownAccountCookie, buildClientContextHeaders, buildFlagContext, buildSecurityHeaders, clearKnownAccountsCookie, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, createServerClient, createWebhookHandler, createWebhookRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getKnownAccountsCookieRaw, getKnownAccountsFromRequest, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, isSafeRedirect, normalizeKnownAccountsCookie, parseWebhookEvent, prefetchBundles, prefetchSecrets, registerVideoWebhook, removeKnownAccountFromCookie, requireAppSecret, requireBundle, requireSession, resolveGatewayUrl, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, validateSafeRedirect, verifyWebhookSignature, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { g as createWebhookHandler } from '../webhook-handler-
|
|
2
|
-
import '
|
|
1
|
+
export { g as createWebhookHandler } from '../webhook-handler-BqzCYRNJ.mjs';
|
|
2
|
+
import '@scalemule/money';
|
|
3
|
+
import '../index-BIIUrnPr.mjs';
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
export { g as createWebhookHandler } from '../webhook-handler-
|
|
2
|
-
import '
|
|
1
|
+
export { g as createWebhookHandler } from '../webhook-handler-BjFbqZuq.js';
|
|
2
|
+
import '@scalemule/money';
|
|
3
|
+
import '../index-BIIUrnPr.js';
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { U as User, A as ApiError,
|
|
3
|
+
import { U as User, A as ApiError, a9 as StorageFile } from './index-BIIUrnPr.mjs';
|
|
4
4
|
|
|
5
5
|
interface MockUserOptions {
|
|
6
6
|
id?: string;
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
-
import { U as User, A as ApiError,
|
|
3
|
+
import { U as User, A as ApiError, a9 as StorageFile } from './index-BIIUrnPr.js';
|
|
4
4
|
|
|
5
5
|
interface MockUserOptions {
|
|
6
6
|
id?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MoneyClient } from '@scalemule/money';
|
|
2
|
+
import { $ as RegisterRequest, p as ClientContext, U as User, v as LoginRequest, L as LoginResponse, c as ListFilesParams, t as ListFilesResponse, a9 as StorageFile, af as UploadResponse } from './index-BIIUrnPr.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Server-Side ScaleMule Client
|
|
@@ -23,7 +24,9 @@ declare class ScaleMuleServer {
|
|
|
23
24
|
private apiKey;
|
|
24
25
|
private gatewayUrl;
|
|
25
26
|
private debug;
|
|
27
|
+
money: MoneyClient;
|
|
26
28
|
constructor(config: ServerConfig);
|
|
29
|
+
moneyWithSession(sessionToken: string): MoneyClient;
|
|
27
30
|
/**
|
|
28
31
|
* Make a request to the ScaleMule API
|
|
29
32
|
*
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MoneyClient } from '@scalemule/money';
|
|
2
|
+
import { $ as RegisterRequest, p as ClientContext, U as User, v as LoginRequest, L as LoginResponse, c as ListFilesParams, t as ListFilesResponse, a9 as StorageFile, af as UploadResponse } from './index-BIIUrnPr.mjs';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Server-Side ScaleMule Client
|
|
@@ -23,7 +24,9 @@ declare class ScaleMuleServer {
|
|
|
23
24
|
private apiKey;
|
|
24
25
|
private gatewayUrl;
|
|
25
26
|
private debug;
|
|
27
|
+
money: MoneyClient;
|
|
26
28
|
constructor(config: ServerConfig);
|
|
29
|
+
moneyWithSession(sessionToken: string): MoneyClient;
|
|
27
30
|
/**
|
|
28
31
|
* Make a request to the ScaleMule API
|
|
29
32
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalemule/nextjs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
4
4
|
"description": "ScaleMule SDK for Next.js applications - authentication, storage, and user management",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -52,7 +52,8 @@
|
|
|
52
52
|
"clean": "rm -rf dist"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@scalemule/
|
|
55
|
+
"@scalemule/money": "^0.0.1",
|
|
56
|
+
"@scalemule/sdk": "^0.0.29"
|
|
56
57
|
},
|
|
57
58
|
"peerDependencies": {
|
|
58
59
|
"next": ">=14.0.0",
|