@scalemule/nextjs 0.0.39 → 0.0.40
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 +135 -4
- package/dist/server/index.d.ts +135 -4
- package/dist/server/index.js +333 -6
- package/dist/server/index.mjs +325 -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.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
var money = require('@scalemule/money');
|
|
3
4
|
var headers = require('next/headers');
|
|
4
5
|
var server = require('next/server');
|
|
5
6
|
var crypto$1 = require('crypto');
|
|
@@ -2667,6 +2668,14 @@ var ScaleMuleServer = class {
|
|
|
2667
2668
|
this.apiKey = config.apiKey;
|
|
2668
2669
|
this.gatewayUrl = resolveGatewayUrl(config);
|
|
2669
2670
|
this.debug = config.debug || false;
|
|
2671
|
+
this.money = money.createMoneyClient({
|
|
2672
|
+
apiKey: this.apiKey,
|
|
2673
|
+
gatewayUrl: this.gatewayUrl,
|
|
2674
|
+
fetch: globalThis.fetch.bind(globalThis)
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
moneyWithSession(sessionToken) {
|
|
2678
|
+
return this.money.withAccessToken(sessionToken);
|
|
2670
2679
|
}
|
|
2671
2680
|
/**
|
|
2672
2681
|
* Make a request to the ScaleMule API
|
|
@@ -2745,6 +2754,7 @@ function createServerClient(config) {
|
|
|
2745
2754
|
}
|
|
2746
2755
|
var SESSION_COOKIE_NAME = "sm_session";
|
|
2747
2756
|
var USER_ID_COOKIE_NAME = "sm_user_id";
|
|
2757
|
+
var KNOWN_ACCOUNTS_COOKIE_NAME = "sm_known_accounts";
|
|
2748
2758
|
({
|
|
2749
2759
|
secure: process.env.NODE_ENV === "production"});
|
|
2750
2760
|
function createCookieHeader(name, value, options = {}) {
|
|
@@ -2845,6 +2855,163 @@ function getSessionFromRequest(request) {
|
|
|
2845
2855
|
expiresAt: /* @__PURE__ */ new Date()
|
|
2846
2856
|
};
|
|
2847
2857
|
}
|
|
2858
|
+
var MAX_KNOWN_ACCOUNTS = 10;
|
|
2859
|
+
function maskEmail(email) {
|
|
2860
|
+
const [local, domain] = email.split("@");
|
|
2861
|
+
if (!domain) return "***@***.***";
|
|
2862
|
+
const tldDot = domain.lastIndexOf(".");
|
|
2863
|
+
const tld = tldDot > 0 ? domain.slice(tldDot) : "";
|
|
2864
|
+
const domainBase = tldDot > 0 ? domain.slice(0, tldDot) : domain;
|
|
2865
|
+
return `${local[0] || "*"}***@${domainBase[0] || "*"}***${tld}`;
|
|
2866
|
+
}
|
|
2867
|
+
function stableColorIndex(userId) {
|
|
2868
|
+
let hash = 0;
|
|
2869
|
+
for (let i = 0; i < userId.length; i++) {
|
|
2870
|
+
hash = (hash << 5) - hash + userId.charCodeAt(i) | 0;
|
|
2871
|
+
}
|
|
2872
|
+
return Math.abs(hash) % 8;
|
|
2873
|
+
}
|
|
2874
|
+
function applyPrivacyToEntry(entry, privacy) {
|
|
2875
|
+
switch (privacy) {
|
|
2876
|
+
case "full":
|
|
2877
|
+
return entry;
|
|
2878
|
+
case "masked":
|
|
2879
|
+
return {
|
|
2880
|
+
userId: entry.userId,
|
|
2881
|
+
email: entry.email ? maskEmail(entry.email) : void 0,
|
|
2882
|
+
fullName: entry.fullName ? `${entry.fullName[0].toUpperCase()}.` : void 0,
|
|
2883
|
+
provider: entry.provider,
|
|
2884
|
+
lastActiveAt: entry.lastActiveAt,
|
|
2885
|
+
colorIndex: stableColorIndex(entry.userId)
|
|
2886
|
+
};
|
|
2887
|
+
case "minimal":
|
|
2888
|
+
return {
|
|
2889
|
+
userId: entry.userId,
|
|
2890
|
+
provider: entry.provider,
|
|
2891
|
+
lastActiveAt: entry.lastActiveAt,
|
|
2892
|
+
displayLabel: "Account",
|
|
2893
|
+
colorIndex: stableColorIndex(entry.userId)
|
|
2894
|
+
};
|
|
2895
|
+
}
|
|
2896
|
+
}
|
|
2897
|
+
function appendKnownAccountCookie(headers2, account, existingCookie, options = {}, privacy) {
|
|
2898
|
+
let accounts = {};
|
|
2899
|
+
if (existingCookie) {
|
|
2900
|
+
try {
|
|
2901
|
+
accounts = JSON.parse(decodeURIComponent(existingCookie));
|
|
2902
|
+
} catch {
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
2905
|
+
const effectivePrivacy = privacy || "full";
|
|
2906
|
+
for (const [userId, entry] of Object.entries(accounts)) {
|
|
2907
|
+
accounts[userId] = applyPrivacyToEntry(entry, effectivePrivacy);
|
|
2908
|
+
}
|
|
2909
|
+
accounts[account.userId] = applyPrivacyToEntry(account, effectivePrivacy);
|
|
2910
|
+
const entries = Object.entries(accounts);
|
|
2911
|
+
if (entries.length > MAX_KNOWN_ACCOUNTS) {
|
|
2912
|
+
entries.sort((a, b) => new Date(b[1].lastActiveAt).getTime() - new Date(a[1].lastActiveAt).getTime());
|
|
2913
|
+
accounts = Object.fromEntries(entries.slice(0, MAX_KNOWN_ACCOUNTS));
|
|
2914
|
+
}
|
|
2915
|
+
const maxAge = 365 * 24 * 60 * 60;
|
|
2916
|
+
const secure = options.secure ?? process.env.NODE_ENV === "production";
|
|
2917
|
+
const sameSite = options.sameSite ?? "lax";
|
|
2918
|
+
const path = options.path ?? "/";
|
|
2919
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(accounts))}; Path=${path}; Max-Age=${maxAge}; SameSite=${sameSite}`;
|
|
2920
|
+
if (secure) {
|
|
2921
|
+
cookie += "; Secure";
|
|
2922
|
+
}
|
|
2923
|
+
if (options.domain) {
|
|
2924
|
+
cookie += `; Domain=${options.domain}`;
|
|
2925
|
+
}
|
|
2926
|
+
headers2.append("Set-Cookie", cookie);
|
|
2927
|
+
}
|
|
2928
|
+
function removeKnownAccountFromCookie(headers2, userId, existingCookie, options = {}) {
|
|
2929
|
+
let accounts = {};
|
|
2930
|
+
if (existingCookie) {
|
|
2931
|
+
try {
|
|
2932
|
+
accounts = JSON.parse(decodeURIComponent(existingCookie));
|
|
2933
|
+
} catch {
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
delete accounts[userId];
|
|
2937
|
+
const maxAge = 365 * 24 * 60 * 60;
|
|
2938
|
+
const secure = options.secure ?? process.env.NODE_ENV === "production";
|
|
2939
|
+
const sameSite = options.sameSite ?? "lax";
|
|
2940
|
+
const path = options.path ?? "/";
|
|
2941
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(accounts))}; Path=${path}; Max-Age=${maxAge}; SameSite=${sameSite}`;
|
|
2942
|
+
if (secure) {
|
|
2943
|
+
cookie += "; Secure";
|
|
2944
|
+
}
|
|
2945
|
+
if (options.domain) {
|
|
2946
|
+
cookie += `; Domain=${options.domain}`;
|
|
2947
|
+
}
|
|
2948
|
+
headers2.append("Set-Cookie", cookie);
|
|
2949
|
+
}
|
|
2950
|
+
function clearKnownAccountsCookie(headers2, options = {}) {
|
|
2951
|
+
const path = options.path ?? "/";
|
|
2952
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=; Path=${path}; Max-Age=0`;
|
|
2953
|
+
if (options.domain) {
|
|
2954
|
+
cookie += `; Domain=${options.domain}`;
|
|
2955
|
+
}
|
|
2956
|
+
headers2.append("Set-Cookie", cookie);
|
|
2957
|
+
}
|
|
2958
|
+
function getKnownAccountsFromRequest(request) {
|
|
2959
|
+
const cookieHeader = request.headers.get("cookie");
|
|
2960
|
+
if (!cookieHeader) return [];
|
|
2961
|
+
const cookies4 = Object.fromEntries(
|
|
2962
|
+
cookieHeader.split(";").map((c) => {
|
|
2963
|
+
const [key, ...rest] = c.trim().split("=");
|
|
2964
|
+
return [key, decodeURIComponent(rest.join("="))];
|
|
2965
|
+
})
|
|
2966
|
+
);
|
|
2967
|
+
const raw = cookies4[KNOWN_ACCOUNTS_COOKIE_NAME];
|
|
2968
|
+
if (!raw) return [];
|
|
2969
|
+
try {
|
|
2970
|
+
const accounts = JSON.parse(raw);
|
|
2971
|
+
return Object.values(accounts);
|
|
2972
|
+
} catch {
|
|
2973
|
+
return [];
|
|
2974
|
+
}
|
|
2975
|
+
}
|
|
2976
|
+
function getKnownAccountsCookieRaw(request) {
|
|
2977
|
+
const cookieHeader = request.headers.get("cookie");
|
|
2978
|
+
if (!cookieHeader) return null;
|
|
2979
|
+
const cookies4 = Object.fromEntries(
|
|
2980
|
+
cookieHeader.split(";").map((c) => {
|
|
2981
|
+
const [key, ...rest] = c.trim().split("=");
|
|
2982
|
+
return [key, decodeURIComponent(rest.join("="))];
|
|
2983
|
+
})
|
|
2984
|
+
);
|
|
2985
|
+
return cookies4[KNOWN_ACCOUNTS_COOKIE_NAME] || null;
|
|
2986
|
+
}
|
|
2987
|
+
function normalizeKnownAccountsCookie(request, privacy, options = {}) {
|
|
2988
|
+
if (!privacy || privacy === "full") return null;
|
|
2989
|
+
const raw = getKnownAccountsCookieRaw(request);
|
|
2990
|
+
if (!raw) return null;
|
|
2991
|
+
let accounts = {};
|
|
2992
|
+
try {
|
|
2993
|
+
accounts = JSON.parse(raw);
|
|
2994
|
+
} catch {
|
|
2995
|
+
return null;
|
|
2996
|
+
}
|
|
2997
|
+
let changed = false;
|
|
2998
|
+
for (const [userId, entry] of Object.entries(accounts)) {
|
|
2999
|
+
const normalized = applyPrivacyToEntry(entry, privacy);
|
|
3000
|
+
if (JSON.stringify(normalized) !== JSON.stringify(entry)) {
|
|
3001
|
+
accounts[userId] = normalized;
|
|
3002
|
+
changed = true;
|
|
3003
|
+
}
|
|
3004
|
+
}
|
|
3005
|
+
if (!changed) return null;
|
|
3006
|
+
const maxAge = 365 * 24 * 60 * 60;
|
|
3007
|
+
const secure = options.secure ?? process.env.NODE_ENV === "production";
|
|
3008
|
+
const sameSite = options.sameSite ?? "lax";
|
|
3009
|
+
const path = options.path ?? "/";
|
|
3010
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(accounts))}; Path=${path}; Max-Age=${maxAge}; SameSite=${sameSite}`;
|
|
3011
|
+
if (secure) cookie += "; Secure";
|
|
3012
|
+
if (options.domain) cookie += `; Domain=${options.domain}`;
|
|
3013
|
+
return cookie;
|
|
3014
|
+
}
|
|
2848
3015
|
async function requireSession() {
|
|
2849
3016
|
const session = await getSession();
|
|
2850
3017
|
if (!session) {
|
|
@@ -3004,7 +3171,25 @@ function createAuthRoutes(config = {}) {
|
|
|
3004
3171
|
} catch {
|
|
3005
3172
|
return successResponse({ user: registeredUser, message: "Registration successful" }, 201);
|
|
3006
3173
|
}
|
|
3007
|
-
|
|
3174
|
+
const registerResponse = withSession(loginData, { user: registeredUser, sessionToken: loginData.session_token, userId: registeredUser.id }, cookieOptions);
|
|
3175
|
+
if (config.enableAccountSwitcher) {
|
|
3176
|
+
const existingKnown = getKnownAccountsCookieRaw(request);
|
|
3177
|
+
appendKnownAccountCookie(
|
|
3178
|
+
registerResponse.headers,
|
|
3179
|
+
{
|
|
3180
|
+
userId: registeredUser.id,
|
|
3181
|
+
email: registeredUser.email,
|
|
3182
|
+
fullName: registeredUser.full_name ?? void 0,
|
|
3183
|
+
avatarUrl: registeredUser.avatar_url ?? void 0,
|
|
3184
|
+
provider: "email",
|
|
3185
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3186
|
+
},
|
|
3187
|
+
existingKnown,
|
|
3188
|
+
cookieOptions,
|
|
3189
|
+
config.accountSwitcherPrivacy
|
|
3190
|
+
);
|
|
3191
|
+
}
|
|
3192
|
+
return registerResponse;
|
|
3008
3193
|
}
|
|
3009
3194
|
// ==================== Login ====================
|
|
3010
3195
|
case "login": {
|
|
@@ -3035,7 +3220,25 @@ function createAuthRoutes(config = {}) {
|
|
|
3035
3220
|
email: loginData.user.email
|
|
3036
3221
|
});
|
|
3037
3222
|
}
|
|
3038
|
-
|
|
3223
|
+
const loginResponse = withSession(loginData, { user: loginData.user, sessionToken: loginData.session_token, userId: loginData.user.id }, cookieOptions);
|
|
3224
|
+
if (config.enableAccountSwitcher) {
|
|
3225
|
+
const existingKnown = getKnownAccountsCookieRaw(request);
|
|
3226
|
+
appendKnownAccountCookie(
|
|
3227
|
+
loginResponse.headers,
|
|
3228
|
+
{
|
|
3229
|
+
userId: loginData.user.id,
|
|
3230
|
+
email: loginData.user.email,
|
|
3231
|
+
fullName: loginData.user.full_name ?? void 0,
|
|
3232
|
+
avatarUrl: loginData.user.avatar_url ?? void 0,
|
|
3233
|
+
provider: "email",
|
|
3234
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3235
|
+
},
|
|
3236
|
+
existingKnown,
|
|
3237
|
+
cookieOptions,
|
|
3238
|
+
config.accountSwitcherPrivacy
|
|
3239
|
+
);
|
|
3240
|
+
}
|
|
3241
|
+
return loginResponse;
|
|
3039
3242
|
}
|
|
3040
3243
|
// ==================== Logout ====================
|
|
3041
3244
|
case "logout": {
|
|
@@ -3182,6 +3385,59 @@ function createAuthRoutes(config = {}) {
|
|
|
3182
3385
|
}
|
|
3183
3386
|
return successResponse({ message: "Password changed successfully" });
|
|
3184
3387
|
}
|
|
3388
|
+
// ==================== Switch Account ====================
|
|
3389
|
+
// Clears the active session so the user can re-authenticate as a different account.
|
|
3390
|
+
// The known accounts cookie is preserved — only the session cookie is cleared.
|
|
3391
|
+
case "switch-account": {
|
|
3392
|
+
if (!config.enableAccountSwitcher) {
|
|
3393
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3394
|
+
}
|
|
3395
|
+
const session = await getSession();
|
|
3396
|
+
if (session) {
|
|
3397
|
+
try {
|
|
3398
|
+
await sm.auth.logout(session.sessionToken);
|
|
3399
|
+
} catch {
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
const switchResponse = clearSession({ message: "Session cleared for account switch" }, cookieOptions);
|
|
3403
|
+
const knownAccounts = getKnownAccountsFromRequest(request);
|
|
3404
|
+
return new Response(
|
|
3405
|
+
JSON.stringify({ success: true, data: { message: "Session cleared for account switch", knownAccounts } }),
|
|
3406
|
+
{ status: 200, headers: switchResponse.headers }
|
|
3407
|
+
);
|
|
3408
|
+
}
|
|
3409
|
+
// ==================== Forget Account ====================
|
|
3410
|
+
// Remove a specific account from the known accounts cookie.
|
|
3411
|
+
case "forget-account": {
|
|
3412
|
+
if (!config.enableAccountSwitcher) {
|
|
3413
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3414
|
+
}
|
|
3415
|
+
const { user_id } = body;
|
|
3416
|
+
if (!user_id) {
|
|
3417
|
+
return errorResponse("VALIDATION_ERROR", "user_id required", 400);
|
|
3418
|
+
}
|
|
3419
|
+
const headers2 = new Headers();
|
|
3420
|
+
headers2.set("Content-Type", "application/json");
|
|
3421
|
+
const existingKnown = getKnownAccountsCookieRaw(request);
|
|
3422
|
+
removeKnownAccountFromCookie(headers2, user_id, existingKnown, cookieOptions);
|
|
3423
|
+
return new Response(
|
|
3424
|
+
JSON.stringify({ success: true, data: { message: "Account forgotten" } }),
|
|
3425
|
+
{ status: 200, headers: headers2 }
|
|
3426
|
+
);
|
|
3427
|
+
}
|
|
3428
|
+
// ==================== Forget All Accounts ====================
|
|
3429
|
+
case "forget-all-accounts": {
|
|
3430
|
+
if (!config.enableAccountSwitcher) {
|
|
3431
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3432
|
+
}
|
|
3433
|
+
const headers2 = new Headers();
|
|
3434
|
+
headers2.set("Content-Type", "application/json");
|
|
3435
|
+
clearKnownAccountsCookie(headers2, cookieOptions);
|
|
3436
|
+
return new Response(
|
|
3437
|
+
JSON.stringify({ success: true, data: { message: "All accounts forgotten" } }),
|
|
3438
|
+
{ status: 200, headers: headers2 }
|
|
3439
|
+
);
|
|
3440
|
+
}
|
|
3185
3441
|
default:
|
|
3186
3442
|
return errorResponse("NOT_FOUND", `Unknown endpoint: ${path}`, 404);
|
|
3187
3443
|
}
|
|
@@ -3197,20 +3453,25 @@ function createAuthRoutes(config = {}) {
|
|
|
3197
3453
|
switch (path) {
|
|
3198
3454
|
// ==================== Get Current User ====================
|
|
3199
3455
|
case "me": {
|
|
3456
|
+
const normCookie = config.enableAccountSwitcher ? normalizeKnownAccountsCookie(request, config.accountSwitcherPrivacy, cookieOptions) : null;
|
|
3457
|
+
const withNorm = (resp) => {
|
|
3458
|
+
if (normCookie) resp.headers.append("Set-Cookie", normCookie);
|
|
3459
|
+
return resp;
|
|
3460
|
+
};
|
|
3200
3461
|
const session = await getSession();
|
|
3201
3462
|
if (!session) {
|
|
3202
|
-
return errorResponse("UNAUTHORIZED", "Authentication required", 401);
|
|
3463
|
+
return withNorm(errorResponse("UNAUTHORIZED", "Authentication required", 401));
|
|
3203
3464
|
}
|
|
3204
3465
|
let userData;
|
|
3205
3466
|
try {
|
|
3206
3467
|
userData = await sm.auth.me(session.sessionToken);
|
|
3207
3468
|
} catch {
|
|
3208
|
-
return clearSession(
|
|
3469
|
+
return withNorm(clearSession(
|
|
3209
3470
|
{ error: { code: "SESSION_EXPIRED", message: "Session expired" } },
|
|
3210
3471
|
cookieOptions
|
|
3211
|
-
);
|
|
3472
|
+
));
|
|
3212
3473
|
}
|
|
3213
|
-
return successResponse({ user: userData, sessionToken: session.sessionToken, userId: session.userId });
|
|
3474
|
+
return withNorm(successResponse({ user: userData, sessionToken: session.sessionToken, userId: session.userId }));
|
|
3214
3475
|
}
|
|
3215
3476
|
// ==================== Get Session Status ====================
|
|
3216
3477
|
case "session": {
|
|
@@ -3220,6 +3481,14 @@ function createAuthRoutes(config = {}) {
|
|
|
3220
3481
|
userId: session?.userId || null
|
|
3221
3482
|
});
|
|
3222
3483
|
}
|
|
3484
|
+
// ==================== Get Known Accounts ====================
|
|
3485
|
+
case "known-accounts": {
|
|
3486
|
+
if (!config.enableAccountSwitcher) {
|
|
3487
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3488
|
+
}
|
|
3489
|
+
const knownAccounts = getKnownAccountsFromRequest(request);
|
|
3490
|
+
return successResponse({ knownAccounts });
|
|
3491
|
+
}
|
|
3223
3492
|
default:
|
|
3224
3493
|
return errorResponse("NOT_FOUND", `Unknown endpoint: ${path}`, 404);
|
|
3225
3494
|
}
|
|
@@ -4827,16 +5096,68 @@ async function prefetchBundles(keys) {
|
|
|
4827
5096
|
await Promise.all(keys.map((key) => getBundle(key)));
|
|
4828
5097
|
}
|
|
4829
5098
|
|
|
5099
|
+
// src/server/redirect.ts
|
|
5100
|
+
function normalizeOrigin(origin) {
|
|
5101
|
+
try {
|
|
5102
|
+
const u = new URL(origin);
|
|
5103
|
+
return `${u.protocol}//${u.host}`.toLowerCase();
|
|
5104
|
+
} catch {
|
|
5105
|
+
return null;
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
function isSafeSchemeRelative(input) {
|
|
5109
|
+
return !(input.startsWith("//") || input.startsWith("\\"));
|
|
5110
|
+
}
|
|
5111
|
+
function validateSafeRedirect(input, opts = {}) {
|
|
5112
|
+
const defaultPath = opts.defaultPath ?? "/";
|
|
5113
|
+
if (!input || typeof input !== "string") return defaultPath;
|
|
5114
|
+
const trimmed = input.trim();
|
|
5115
|
+
if (!trimmed) return defaultPath;
|
|
5116
|
+
if (!isSafeSchemeRelative(trimmed)) return defaultPath;
|
|
5117
|
+
const colon = trimmed.indexOf(":");
|
|
5118
|
+
if (colon > 0 && colon < 15) {
|
|
5119
|
+
const scheme = trimmed.slice(0, colon).toLowerCase();
|
|
5120
|
+
if (/^[a-z][a-z0-9+.-]*$/.test(scheme) && scheme !== "http" && scheme !== "https") {
|
|
5121
|
+
return defaultPath;
|
|
5122
|
+
}
|
|
5123
|
+
}
|
|
5124
|
+
if (trimmed.startsWith("/")) {
|
|
5125
|
+
if (trimmed.length > 1 && trimmed[1] === "\\") return defaultPath;
|
|
5126
|
+
return trimmed;
|
|
5127
|
+
}
|
|
5128
|
+
const allowed = (opts.allowedOrigins ?? []).map(normalizeOrigin).filter((o) => o !== null);
|
|
5129
|
+
try {
|
|
5130
|
+
const parsed = new URL(trimmed);
|
|
5131
|
+
const origin = `${parsed.protocol}//${parsed.host}`.toLowerCase();
|
|
5132
|
+
if (!allowed.includes(origin)) return defaultPath;
|
|
5133
|
+
if (opts.stripSameOriginHost !== false) {
|
|
5134
|
+
return `${parsed.pathname}${parsed.search}${parsed.hash}` || "/";
|
|
5135
|
+
}
|
|
5136
|
+
return trimmed;
|
|
5137
|
+
} catch {
|
|
5138
|
+
return defaultPath;
|
|
5139
|
+
}
|
|
5140
|
+
}
|
|
5141
|
+
function isSafeRedirect(input, opts = {}) {
|
|
5142
|
+
if (!input || typeof input !== "string") return false;
|
|
5143
|
+
const defaultPath = `__unsafe_sentinel_${Math.random()}`;
|
|
5144
|
+
const resolved = validateSafeRedirect(input, { ...opts, defaultPath });
|
|
5145
|
+
return resolved !== defaultPath;
|
|
5146
|
+
}
|
|
5147
|
+
|
|
4830
5148
|
exports.CSRF_COOKIE_NAME = CSRF_COOKIE_NAME;
|
|
4831
5149
|
exports.CSRF_HEADER_NAME = CSRF_HEADER_NAME;
|
|
5150
|
+
exports.KNOWN_ACCOUNTS_COOKIE_NAME = KNOWN_ACCOUNTS_COOKIE_NAME;
|
|
4832
5151
|
exports.OAUTH_STATE_COOKIE_NAME = OAUTH_STATE_COOKIE_NAME;
|
|
4833
5152
|
exports.SESSION_COOKIE_NAME = SESSION_COOKIE_NAME;
|
|
4834
5153
|
exports.ScaleMuleError = ScaleMuleError;
|
|
4835
5154
|
exports.ScaleMuleServer = ScaleMuleServer;
|
|
4836
5155
|
exports.USER_ID_COOKIE_NAME = USER_ID_COOKIE_NAME;
|
|
4837
5156
|
exports.apiHandler = apiHandler;
|
|
5157
|
+
exports.appendKnownAccountCookie = appendKnownAccountCookie;
|
|
4838
5158
|
exports.buildClientContextHeaders = buildClientContextHeaders;
|
|
4839
5159
|
exports.buildFlagContext = buildFlagContext;
|
|
5160
|
+
exports.clearKnownAccountsCookie = clearKnownAccountsCookie;
|
|
4840
5161
|
exports.clearOAuthState = clearOAuthState;
|
|
4841
5162
|
exports.clearSession = clearSession;
|
|
4842
5163
|
exports.configureBundles = configureBundles;
|
|
@@ -4858,6 +5179,8 @@ exports.getAppSecretOrDefault = getAppSecretOrDefault;
|
|
|
4858
5179
|
exports.getBootstrapFlags = getBootstrapFlags;
|
|
4859
5180
|
exports.getBundle = getBundle;
|
|
4860
5181
|
exports.getCSRFToken = getCSRFToken;
|
|
5182
|
+
exports.getKnownAccountsCookieRaw = getKnownAccountsCookieRaw;
|
|
5183
|
+
exports.getKnownAccountsFromRequest = getKnownAccountsFromRequest;
|
|
4861
5184
|
exports.getMySqlBundle = getMySqlBundle;
|
|
4862
5185
|
exports.getOAuthBundle = getOAuthBundle;
|
|
4863
5186
|
exports.getPostgresBundle = getPostgresBundle;
|
|
@@ -4868,10 +5191,13 @@ exports.getSessionFromRequest = getSessionFromRequest;
|
|
|
4868
5191
|
exports.getSmtpBundle = getSmtpBundle;
|
|
4869
5192
|
exports.invalidateBundleCache = invalidateBundleCache;
|
|
4870
5193
|
exports.invalidateSecretCache = invalidateSecretCache;
|
|
5194
|
+
exports.isSafeRedirect = isSafeRedirect;
|
|
5195
|
+
exports.normalizeKnownAccountsCookie = normalizeKnownAccountsCookie;
|
|
4871
5196
|
exports.parseWebhookEvent = parseWebhookEvent;
|
|
4872
5197
|
exports.prefetchBundles = prefetchBundles;
|
|
4873
5198
|
exports.prefetchSecrets = prefetchSecrets;
|
|
4874
5199
|
exports.registerVideoWebhook = registerVideoWebhook;
|
|
5200
|
+
exports.removeKnownAccountFromCookie = removeKnownAccountFromCookie;
|
|
4875
5201
|
exports.requireAppSecret = requireAppSecret;
|
|
4876
5202
|
exports.requireBundle = requireBundle;
|
|
4877
5203
|
exports.requireSession = requireSession;
|
|
@@ -4882,6 +5208,7 @@ exports.validateCSRFToken = validateCSRFToken;
|
|
|
4882
5208
|
exports.validateCSRFTokenAsync = validateCSRFTokenAsync;
|
|
4883
5209
|
exports.validateOAuthState = validateOAuthState;
|
|
4884
5210
|
exports.validateOAuthStateAsync = validateOAuthStateAsync;
|
|
5211
|
+
exports.validateSafeRedirect = validateSafeRedirect;
|
|
4885
5212
|
exports.verifyWebhookSignature = verifyWebhookSignature;
|
|
4886
5213
|
exports.withAuth = withAuth;
|
|
4887
5214
|
exports.withCSRFProtection = withCSRFProtection;
|