@scalemule/nextjs 0.0.35 → 0.0.37
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 +7 -3
- package/dist/index.d.ts +7 -3
- package/dist/index.js +439 -78
- package/dist/index.mjs +439 -78
- package/dist/server/auth.js +266 -6
- package/dist/server/auth.mjs +266 -6
- package/dist/server/index.d.mts +68 -4
- package/dist/server/index.d.ts +68 -4
- package/dist/server/index.js +273 -6
- package/dist/server/index.mjs +267 -7
- package/dist/server/webhook-handler.d.mts +2 -2
- package/dist/server/webhook-handler.d.ts +2 -2
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{webhook-handler-CjSSCdvI.d.ts → webhook-handler-BobYTuNP.d.ts} +1 -1
- package/dist/{webhook-handler-Cz9jtet2.d.mts → webhook-handler-CNKQ1k2a.d.mts} +1 -1
- package/package.json +2 -2
package/dist/server/index.d.mts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { S as ServerConfig } from '../webhook-handler-
|
|
2
|
-
export { a as ScaleMuleServer, V as VideoFailedEvent, b as VideoReadyEvent, c as VideoTranscodedEvent, d as VideoUploadedEvent, W as WebhookEvent, e as WebhookRoutesConfig, f as createServerClient, g as createWebhookHandler, h as createWebhookRoutes, p as parseWebhookEvent, r as registerVideoWebhook, i as resolveGatewayUrl, v as verifyWebhookSignature } from '../webhook-handler-
|
|
3
|
-
import { p as ClientContext, A as ApiError } from '../index-
|
|
1
|
+
import { S as ServerConfig } from '../webhook-handler-CNKQ1k2a.mjs';
|
|
2
|
+
export { a as ScaleMuleServer, V as VideoFailedEvent, b as VideoReadyEvent, c as VideoTranscodedEvent, d as VideoUploadedEvent, W as WebhookEvent, e as WebhookRoutesConfig, f as createServerClient, g as createWebhookHandler, h as createWebhookRoutes, p as parseWebhookEvent, r as registerVideoWebhook, i as resolveGatewayUrl, v as verifyWebhookSignature } from '../webhook-handler-CNKQ1k2a.mjs';
|
|
3
|
+
import { p as ClientContext, A as ApiError } from '../index-BIIUrnPr.mjs';
|
|
4
4
|
import { NextRequest, NextResponse } from 'next/server';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -110,6 +110,12 @@ declare function buildFlagContext(clientContext: Pick<ClientContext, 'ip'> | und
|
|
|
110
110
|
*/
|
|
111
111
|
declare const SESSION_COOKIE_NAME = "sm_session";
|
|
112
112
|
declare const USER_ID_COOKIE_NAME = "sm_user_id";
|
|
113
|
+
/**
|
|
114
|
+
* Known accounts cookie — stores display metadata (email, name, avatar) for
|
|
115
|
+
* accounts that have logged in on this device. NOT httpOnly so client JS can
|
|
116
|
+
* read it to render the account switcher UI. Contains NO tokens or secrets.
|
|
117
|
+
*/
|
|
118
|
+
declare const KNOWN_ACCOUNTS_COOKIE_NAME = "sm_known_accounts";
|
|
113
119
|
interface SessionCookieOptions {
|
|
114
120
|
/** Cookie max age in seconds (default: 7 days) */
|
|
115
121
|
maxAge?: number;
|
|
@@ -181,6 +187,51 @@ declare function getSession(): Promise<SessionData | null>;
|
|
|
181
187
|
* Use this when you need to read cookies from a Request directly.
|
|
182
188
|
*/
|
|
183
189
|
declare function getSessionFromRequest(request: Request): SessionData | null;
|
|
190
|
+
/**
|
|
191
|
+
* Known account entry stored in cookie.
|
|
192
|
+
* Contains display metadata ONLY — no tokens, no secrets.
|
|
193
|
+
*/
|
|
194
|
+
interface KnownAccountEntry {
|
|
195
|
+
userId: string;
|
|
196
|
+
email?: string;
|
|
197
|
+
fullName?: string;
|
|
198
|
+
avatarUrl?: string;
|
|
199
|
+
provider?: string;
|
|
200
|
+
lastActiveAt: string;
|
|
201
|
+
displayLabel?: string;
|
|
202
|
+
colorIndex?: number;
|
|
203
|
+
}
|
|
204
|
+
type AccountSwitcherPrivacy = 'full' | 'masked' | 'minimal';
|
|
205
|
+
/**
|
|
206
|
+
* Add an account to the known accounts cookie.
|
|
207
|
+
* Called after successful login. The cookie is NOT httpOnly so client JS
|
|
208
|
+
* can read it to render the account switcher UI.
|
|
209
|
+
*
|
|
210
|
+
* Appends Set-Cookie headers to an existing Headers object.
|
|
211
|
+
*/
|
|
212
|
+
declare function appendKnownAccountCookie(headers: Headers, account: KnownAccountEntry, existingCookie: string | null, options?: SessionCookieOptions, privacy?: AccountSwitcherPrivacy): void;
|
|
213
|
+
/**
|
|
214
|
+
* Remove a specific account from the known accounts cookie.
|
|
215
|
+
*/
|
|
216
|
+
declare function removeKnownAccountFromCookie(headers: Headers, userId: string, existingCookie: string | null, options?: SessionCookieOptions): void;
|
|
217
|
+
/**
|
|
218
|
+
* Clear the known accounts cookie entirely.
|
|
219
|
+
*/
|
|
220
|
+
declare function clearKnownAccountsCookie(headers: Headers, options?: SessionCookieOptions): void;
|
|
221
|
+
/**
|
|
222
|
+
* Read known accounts from a Request's cookies.
|
|
223
|
+
*/
|
|
224
|
+
declare function getKnownAccountsFromRequest(request: Request): KnownAccountEntry[];
|
|
225
|
+
/**
|
|
226
|
+
* Read the raw known accounts cookie value from a Request.
|
|
227
|
+
*/
|
|
228
|
+
declare function getKnownAccountsCookieRaw(request: Request): string | null;
|
|
229
|
+
/**
|
|
230
|
+
* Normalize all entries in the known accounts cookie to the given privacy level.
|
|
231
|
+
* Returns a Set-Cookie header string if any entries changed, or null if nothing changed.
|
|
232
|
+
* Used on /me requests to migrate legacy full-PII cookies to the configured privacy level.
|
|
233
|
+
*/
|
|
234
|
+
declare function normalizeKnownAccountsCookie(request: Request, privacy: AccountSwitcherPrivacy | undefined, options?: SessionCookieOptions): string | null;
|
|
184
235
|
/**
|
|
185
236
|
* Require authentication - throws Response if not authenticated
|
|
186
237
|
*
|
|
@@ -220,6 +271,19 @@ interface AuthRoutesConfig {
|
|
|
220
271
|
cookies?: SessionCookieOptions;
|
|
221
272
|
/** Enable CSRF validation on state-changing requests (POST/DELETE/PATCH) */
|
|
222
273
|
csrf?: boolean;
|
|
274
|
+
/**
|
|
275
|
+
* Enable the account switcher — remembers which accounts have logged in on
|
|
276
|
+
* this device (metadata only, no tokens). Switching requires re-authentication.
|
|
277
|
+
* Adds routes: switch-account, forget-account, forget-all-accounts, known-accounts.
|
|
278
|
+
*/
|
|
279
|
+
enableAccountSwitcher?: boolean;
|
|
280
|
+
/**
|
|
281
|
+
* Privacy level for account switcher display metadata.
|
|
282
|
+
* - 'full': Store email, name, avatar as-is (default)
|
|
283
|
+
* - 'masked': Mask email, truncate name to initial
|
|
284
|
+
* - 'minimal': No PII — just a colored "Account" label
|
|
285
|
+
*/
|
|
286
|
+
accountSwitcherPrivacy?: 'full' | 'masked' | 'minimal';
|
|
223
287
|
/** Callbacks */
|
|
224
288
|
onLogin?: (user: {
|
|
225
289
|
id: string;
|
|
@@ -1011,4 +1075,4 @@ declare function invalidateBundleCache(key?: string): void;
|
|
|
1011
1075
|
*/
|
|
1012
1076
|
declare function prefetchBundles(keys: string[]): Promise<void>;
|
|
1013
1077
|
|
|
1014
|
-
export { type AnalyticsRoutesConfig, type AnalyticsTrackingGateConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, type MySqlBundle, type NotificationRoutesConfig, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type PushRoutesConfig, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, buildFlagContext, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, prefetchBundles, prefetchSecrets, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
|
1078
|
+
export { type AnalyticsRoutesConfig, type AnalyticsTrackingGateConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, KNOWN_ACCOUNTS_COOKIE_NAME, type KnownAccountEntry, type MySqlBundle, type NotificationRoutesConfig, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type PushRoutesConfig, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, appendKnownAccountCookie, buildClientContextHeaders, buildFlagContext, clearKnownAccountsCookie, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getKnownAccountsCookieRaw, getKnownAccountsFromRequest, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, normalizeKnownAccountsCookie, prefetchBundles, prefetchSecrets, removeKnownAccountFromCookie, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { S as ServerConfig } from '../webhook-handler-
|
|
2
|
-
export { a as ScaleMuleServer, V as VideoFailedEvent, b as VideoReadyEvent, c as VideoTranscodedEvent, d as VideoUploadedEvent, W as WebhookEvent, e as WebhookRoutesConfig, f as createServerClient, g as createWebhookHandler, h as createWebhookRoutes, p as parseWebhookEvent, r as registerVideoWebhook, i as resolveGatewayUrl, v as verifyWebhookSignature } from '../webhook-handler-
|
|
3
|
-
import { p as ClientContext, A as ApiError } from '../index-
|
|
1
|
+
import { S as ServerConfig } from '../webhook-handler-BobYTuNP.js';
|
|
2
|
+
export { a as ScaleMuleServer, V as VideoFailedEvent, b as VideoReadyEvent, c as VideoTranscodedEvent, d as VideoUploadedEvent, W as WebhookEvent, e as WebhookRoutesConfig, f as createServerClient, g as createWebhookHandler, h as createWebhookRoutes, p as parseWebhookEvent, r as registerVideoWebhook, i as resolveGatewayUrl, v as verifyWebhookSignature } from '../webhook-handler-BobYTuNP.js';
|
|
3
|
+
import { p as ClientContext, A as ApiError } from '../index-BIIUrnPr.js';
|
|
4
4
|
import { NextRequest, NextResponse } from 'next/server';
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -110,6 +110,12 @@ declare function buildFlagContext(clientContext: Pick<ClientContext, 'ip'> | und
|
|
|
110
110
|
*/
|
|
111
111
|
declare const SESSION_COOKIE_NAME = "sm_session";
|
|
112
112
|
declare const USER_ID_COOKIE_NAME = "sm_user_id";
|
|
113
|
+
/**
|
|
114
|
+
* Known accounts cookie — stores display metadata (email, name, avatar) for
|
|
115
|
+
* accounts that have logged in on this device. NOT httpOnly so client JS can
|
|
116
|
+
* read it to render the account switcher UI. Contains NO tokens or secrets.
|
|
117
|
+
*/
|
|
118
|
+
declare const KNOWN_ACCOUNTS_COOKIE_NAME = "sm_known_accounts";
|
|
113
119
|
interface SessionCookieOptions {
|
|
114
120
|
/** Cookie max age in seconds (default: 7 days) */
|
|
115
121
|
maxAge?: number;
|
|
@@ -181,6 +187,51 @@ declare function getSession(): Promise<SessionData | null>;
|
|
|
181
187
|
* Use this when you need to read cookies from a Request directly.
|
|
182
188
|
*/
|
|
183
189
|
declare function getSessionFromRequest(request: Request): SessionData | null;
|
|
190
|
+
/**
|
|
191
|
+
* Known account entry stored in cookie.
|
|
192
|
+
* Contains display metadata ONLY — no tokens, no secrets.
|
|
193
|
+
*/
|
|
194
|
+
interface KnownAccountEntry {
|
|
195
|
+
userId: string;
|
|
196
|
+
email?: string;
|
|
197
|
+
fullName?: string;
|
|
198
|
+
avatarUrl?: string;
|
|
199
|
+
provider?: string;
|
|
200
|
+
lastActiveAt: string;
|
|
201
|
+
displayLabel?: string;
|
|
202
|
+
colorIndex?: number;
|
|
203
|
+
}
|
|
204
|
+
type AccountSwitcherPrivacy = 'full' | 'masked' | 'minimal';
|
|
205
|
+
/**
|
|
206
|
+
* Add an account to the known accounts cookie.
|
|
207
|
+
* Called after successful login. The cookie is NOT httpOnly so client JS
|
|
208
|
+
* can read it to render the account switcher UI.
|
|
209
|
+
*
|
|
210
|
+
* Appends Set-Cookie headers to an existing Headers object.
|
|
211
|
+
*/
|
|
212
|
+
declare function appendKnownAccountCookie(headers: Headers, account: KnownAccountEntry, existingCookie: string | null, options?: SessionCookieOptions, privacy?: AccountSwitcherPrivacy): void;
|
|
213
|
+
/**
|
|
214
|
+
* Remove a specific account from the known accounts cookie.
|
|
215
|
+
*/
|
|
216
|
+
declare function removeKnownAccountFromCookie(headers: Headers, userId: string, existingCookie: string | null, options?: SessionCookieOptions): void;
|
|
217
|
+
/**
|
|
218
|
+
* Clear the known accounts cookie entirely.
|
|
219
|
+
*/
|
|
220
|
+
declare function clearKnownAccountsCookie(headers: Headers, options?: SessionCookieOptions): void;
|
|
221
|
+
/**
|
|
222
|
+
* Read known accounts from a Request's cookies.
|
|
223
|
+
*/
|
|
224
|
+
declare function getKnownAccountsFromRequest(request: Request): KnownAccountEntry[];
|
|
225
|
+
/**
|
|
226
|
+
* Read the raw known accounts cookie value from a Request.
|
|
227
|
+
*/
|
|
228
|
+
declare function getKnownAccountsCookieRaw(request: Request): string | null;
|
|
229
|
+
/**
|
|
230
|
+
* Normalize all entries in the known accounts cookie to the given privacy level.
|
|
231
|
+
* Returns a Set-Cookie header string if any entries changed, or null if nothing changed.
|
|
232
|
+
* Used on /me requests to migrate legacy full-PII cookies to the configured privacy level.
|
|
233
|
+
*/
|
|
234
|
+
declare function normalizeKnownAccountsCookie(request: Request, privacy: AccountSwitcherPrivacy | undefined, options?: SessionCookieOptions): string | null;
|
|
184
235
|
/**
|
|
185
236
|
* Require authentication - throws Response if not authenticated
|
|
186
237
|
*
|
|
@@ -220,6 +271,19 @@ interface AuthRoutesConfig {
|
|
|
220
271
|
cookies?: SessionCookieOptions;
|
|
221
272
|
/** Enable CSRF validation on state-changing requests (POST/DELETE/PATCH) */
|
|
222
273
|
csrf?: boolean;
|
|
274
|
+
/**
|
|
275
|
+
* Enable the account switcher — remembers which accounts have logged in on
|
|
276
|
+
* this device (metadata only, no tokens). Switching requires re-authentication.
|
|
277
|
+
* Adds routes: switch-account, forget-account, forget-all-accounts, known-accounts.
|
|
278
|
+
*/
|
|
279
|
+
enableAccountSwitcher?: boolean;
|
|
280
|
+
/**
|
|
281
|
+
* Privacy level for account switcher display metadata.
|
|
282
|
+
* - 'full': Store email, name, avatar as-is (default)
|
|
283
|
+
* - 'masked': Mask email, truncate name to initial
|
|
284
|
+
* - 'minimal': No PII — just a colored "Account" label
|
|
285
|
+
*/
|
|
286
|
+
accountSwitcherPrivacy?: 'full' | 'masked' | 'minimal';
|
|
223
287
|
/** Callbacks */
|
|
224
288
|
onLogin?: (user: {
|
|
225
289
|
id: string;
|
|
@@ -1011,4 +1075,4 @@ declare function invalidateBundleCache(key?: string): void;
|
|
|
1011
1075
|
*/
|
|
1012
1076
|
declare function prefetchBundles(keys: string[]): Promise<void>;
|
|
1013
1077
|
|
|
1014
|
-
export { type AnalyticsRoutesConfig, type AnalyticsTrackingGateConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, type MySqlBundle, type NotificationRoutesConfig, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type PushRoutesConfig, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, buildClientContextHeaders, buildFlagContext, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, prefetchBundles, prefetchSecrets, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
|
1078
|
+
export { type AnalyticsRoutesConfig, type AnalyticsTrackingGateConfig, type AuthMiddlewareConfig, type AuthRoutesConfig, CSRF_COOKIE_NAME, CSRF_HEADER_NAME, type HandlerContext, type HandlerOptions, KNOWN_ACCOUNTS_COOKIE_NAME, type KnownAccountEntry, type MySqlBundle, type NotificationRoutesConfig, OAUTH_STATE_COOKIE_NAME, type OAuthBundle, type PostgresBundle, type PushRoutesConfig, type RedisBundle, type S3Bundle, SESSION_COOKIE_NAME, ScaleMuleError, ServerConfig, type SessionCookieOptions, type SessionData, type SmtpBundle, USER_ID_COOKIE_NAME, apiHandler, appendKnownAccountCookie, buildClientContextHeaders, buildFlagContext, clearKnownAccountsCookie, clearOAuthState, clearSession, configureBundles, configureSecrets, createAnalyticsRoutes, createAuthMiddleware, createAuthRoutes, createNotificationRoutes, createPushRoutes, errorCodeToStatus, extractClientContext, extractClientContextFromReq, generateCSRFToken, getAppSecret, getAppSecretOrDefault, getBootstrapFlags, getBundle, getCSRFToken, getKnownAccountsCookieRaw, getKnownAccountsFromRequest, getMySqlBundle, getOAuthBundle, getPostgresBundle, getRedisBundle, getS3Bundle, getSession, getSessionFromRequest, getSmtpBundle, invalidateBundleCache, invalidateSecretCache, normalizeKnownAccountsCookie, prefetchBundles, prefetchSecrets, removeKnownAccountFromCookie, requireAppSecret, requireBundle, requireSession, setOAuthState, unwrap, validateCSRFToken, validateCSRFTokenAsync, validateOAuthState, validateOAuthStateAsync, withAuth, withCSRFProtection, withCSRFToken, withSession };
|
package/dist/server/index.js
CHANGED
|
@@ -2745,6 +2745,7 @@ function createServerClient(config) {
|
|
|
2745
2745
|
}
|
|
2746
2746
|
var SESSION_COOKIE_NAME = "sm_session";
|
|
2747
2747
|
var USER_ID_COOKIE_NAME = "sm_user_id";
|
|
2748
|
+
var KNOWN_ACCOUNTS_COOKIE_NAME = "sm_known_accounts";
|
|
2748
2749
|
({
|
|
2749
2750
|
secure: process.env.NODE_ENV === "production"});
|
|
2750
2751
|
function createCookieHeader(name, value, options = {}) {
|
|
@@ -2845,6 +2846,163 @@ function getSessionFromRequest(request) {
|
|
|
2845
2846
|
expiresAt: /* @__PURE__ */ new Date()
|
|
2846
2847
|
};
|
|
2847
2848
|
}
|
|
2849
|
+
var MAX_KNOWN_ACCOUNTS = 10;
|
|
2850
|
+
function maskEmail(email) {
|
|
2851
|
+
const [local, domain] = email.split("@");
|
|
2852
|
+
if (!domain) return "***@***.***";
|
|
2853
|
+
const tldDot = domain.lastIndexOf(".");
|
|
2854
|
+
const tld = tldDot > 0 ? domain.slice(tldDot) : "";
|
|
2855
|
+
const domainBase = tldDot > 0 ? domain.slice(0, tldDot) : domain;
|
|
2856
|
+
return `${local[0] || "*"}***@${domainBase[0] || "*"}***${tld}`;
|
|
2857
|
+
}
|
|
2858
|
+
function stableColorIndex(userId) {
|
|
2859
|
+
let hash = 0;
|
|
2860
|
+
for (let i = 0; i < userId.length; i++) {
|
|
2861
|
+
hash = (hash << 5) - hash + userId.charCodeAt(i) | 0;
|
|
2862
|
+
}
|
|
2863
|
+
return Math.abs(hash) % 8;
|
|
2864
|
+
}
|
|
2865
|
+
function applyPrivacyToEntry(entry, privacy) {
|
|
2866
|
+
switch (privacy) {
|
|
2867
|
+
case "full":
|
|
2868
|
+
return entry;
|
|
2869
|
+
case "masked":
|
|
2870
|
+
return {
|
|
2871
|
+
userId: entry.userId,
|
|
2872
|
+
email: entry.email ? maskEmail(entry.email) : void 0,
|
|
2873
|
+
fullName: entry.fullName ? `${entry.fullName[0].toUpperCase()}.` : void 0,
|
|
2874
|
+
provider: entry.provider,
|
|
2875
|
+
lastActiveAt: entry.lastActiveAt,
|
|
2876
|
+
colorIndex: stableColorIndex(entry.userId)
|
|
2877
|
+
};
|
|
2878
|
+
case "minimal":
|
|
2879
|
+
return {
|
|
2880
|
+
userId: entry.userId,
|
|
2881
|
+
provider: entry.provider,
|
|
2882
|
+
lastActiveAt: entry.lastActiveAt,
|
|
2883
|
+
displayLabel: "Account",
|
|
2884
|
+
colorIndex: stableColorIndex(entry.userId)
|
|
2885
|
+
};
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
function appendKnownAccountCookie(headers2, account, existingCookie, options = {}, privacy) {
|
|
2889
|
+
let accounts = {};
|
|
2890
|
+
if (existingCookie) {
|
|
2891
|
+
try {
|
|
2892
|
+
accounts = JSON.parse(decodeURIComponent(existingCookie));
|
|
2893
|
+
} catch {
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
const effectivePrivacy = privacy || "full";
|
|
2897
|
+
for (const [userId, entry] of Object.entries(accounts)) {
|
|
2898
|
+
accounts[userId] = applyPrivacyToEntry(entry, effectivePrivacy);
|
|
2899
|
+
}
|
|
2900
|
+
accounts[account.userId] = applyPrivacyToEntry(account, effectivePrivacy);
|
|
2901
|
+
const entries = Object.entries(accounts);
|
|
2902
|
+
if (entries.length > MAX_KNOWN_ACCOUNTS) {
|
|
2903
|
+
entries.sort((a, b) => new Date(b[1].lastActiveAt).getTime() - new Date(a[1].lastActiveAt).getTime());
|
|
2904
|
+
accounts = Object.fromEntries(entries.slice(0, MAX_KNOWN_ACCOUNTS));
|
|
2905
|
+
}
|
|
2906
|
+
const maxAge = 365 * 24 * 60 * 60;
|
|
2907
|
+
const secure = options.secure ?? process.env.NODE_ENV === "production";
|
|
2908
|
+
const sameSite = options.sameSite ?? "lax";
|
|
2909
|
+
const path = options.path ?? "/";
|
|
2910
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(accounts))}; Path=${path}; Max-Age=${maxAge}; SameSite=${sameSite}`;
|
|
2911
|
+
if (secure) {
|
|
2912
|
+
cookie += "; Secure";
|
|
2913
|
+
}
|
|
2914
|
+
if (options.domain) {
|
|
2915
|
+
cookie += `; Domain=${options.domain}`;
|
|
2916
|
+
}
|
|
2917
|
+
headers2.append("Set-Cookie", cookie);
|
|
2918
|
+
}
|
|
2919
|
+
function removeKnownAccountFromCookie(headers2, userId, existingCookie, options = {}) {
|
|
2920
|
+
let accounts = {};
|
|
2921
|
+
if (existingCookie) {
|
|
2922
|
+
try {
|
|
2923
|
+
accounts = JSON.parse(decodeURIComponent(existingCookie));
|
|
2924
|
+
} catch {
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
delete accounts[userId];
|
|
2928
|
+
const maxAge = 365 * 24 * 60 * 60;
|
|
2929
|
+
const secure = options.secure ?? process.env.NODE_ENV === "production";
|
|
2930
|
+
const sameSite = options.sameSite ?? "lax";
|
|
2931
|
+
const path = options.path ?? "/";
|
|
2932
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(accounts))}; Path=${path}; Max-Age=${maxAge}; SameSite=${sameSite}`;
|
|
2933
|
+
if (secure) {
|
|
2934
|
+
cookie += "; Secure";
|
|
2935
|
+
}
|
|
2936
|
+
if (options.domain) {
|
|
2937
|
+
cookie += `; Domain=${options.domain}`;
|
|
2938
|
+
}
|
|
2939
|
+
headers2.append("Set-Cookie", cookie);
|
|
2940
|
+
}
|
|
2941
|
+
function clearKnownAccountsCookie(headers2, options = {}) {
|
|
2942
|
+
const path = options.path ?? "/";
|
|
2943
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=; Path=${path}; Max-Age=0`;
|
|
2944
|
+
if (options.domain) {
|
|
2945
|
+
cookie += `; Domain=${options.domain}`;
|
|
2946
|
+
}
|
|
2947
|
+
headers2.append("Set-Cookie", cookie);
|
|
2948
|
+
}
|
|
2949
|
+
function getKnownAccountsFromRequest(request) {
|
|
2950
|
+
const cookieHeader = request.headers.get("cookie");
|
|
2951
|
+
if (!cookieHeader) return [];
|
|
2952
|
+
const cookies4 = Object.fromEntries(
|
|
2953
|
+
cookieHeader.split(";").map((c) => {
|
|
2954
|
+
const [key, ...rest] = c.trim().split("=");
|
|
2955
|
+
return [key, decodeURIComponent(rest.join("="))];
|
|
2956
|
+
})
|
|
2957
|
+
);
|
|
2958
|
+
const raw = cookies4[KNOWN_ACCOUNTS_COOKIE_NAME];
|
|
2959
|
+
if (!raw) return [];
|
|
2960
|
+
try {
|
|
2961
|
+
const accounts = JSON.parse(raw);
|
|
2962
|
+
return Object.values(accounts);
|
|
2963
|
+
} catch {
|
|
2964
|
+
return [];
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
function getKnownAccountsCookieRaw(request) {
|
|
2968
|
+
const cookieHeader = request.headers.get("cookie");
|
|
2969
|
+
if (!cookieHeader) return null;
|
|
2970
|
+
const cookies4 = Object.fromEntries(
|
|
2971
|
+
cookieHeader.split(";").map((c) => {
|
|
2972
|
+
const [key, ...rest] = c.trim().split("=");
|
|
2973
|
+
return [key, decodeURIComponent(rest.join("="))];
|
|
2974
|
+
})
|
|
2975
|
+
);
|
|
2976
|
+
return cookies4[KNOWN_ACCOUNTS_COOKIE_NAME] || null;
|
|
2977
|
+
}
|
|
2978
|
+
function normalizeKnownAccountsCookie(request, privacy, options = {}) {
|
|
2979
|
+
if (!privacy || privacy === "full") return null;
|
|
2980
|
+
const raw = getKnownAccountsCookieRaw(request);
|
|
2981
|
+
if (!raw) return null;
|
|
2982
|
+
let accounts = {};
|
|
2983
|
+
try {
|
|
2984
|
+
accounts = JSON.parse(raw);
|
|
2985
|
+
} catch {
|
|
2986
|
+
return null;
|
|
2987
|
+
}
|
|
2988
|
+
let changed = false;
|
|
2989
|
+
for (const [userId, entry] of Object.entries(accounts)) {
|
|
2990
|
+
const normalized = applyPrivacyToEntry(entry, privacy);
|
|
2991
|
+
if (JSON.stringify(normalized) !== JSON.stringify(entry)) {
|
|
2992
|
+
accounts[userId] = normalized;
|
|
2993
|
+
changed = true;
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
if (!changed) return null;
|
|
2997
|
+
const maxAge = 365 * 24 * 60 * 60;
|
|
2998
|
+
const secure = options.secure ?? process.env.NODE_ENV === "production";
|
|
2999
|
+
const sameSite = options.sameSite ?? "lax";
|
|
3000
|
+
const path = options.path ?? "/";
|
|
3001
|
+
let cookie = `${KNOWN_ACCOUNTS_COOKIE_NAME}=${encodeURIComponent(JSON.stringify(accounts))}; Path=${path}; Max-Age=${maxAge}; SameSite=${sameSite}`;
|
|
3002
|
+
if (secure) cookie += "; Secure";
|
|
3003
|
+
if (options.domain) cookie += `; Domain=${options.domain}`;
|
|
3004
|
+
return cookie;
|
|
3005
|
+
}
|
|
2848
3006
|
async function requireSession() {
|
|
2849
3007
|
const session = await getSession();
|
|
2850
3008
|
if (!session) {
|
|
@@ -3004,7 +3162,25 @@ function createAuthRoutes(config = {}) {
|
|
|
3004
3162
|
} catch {
|
|
3005
3163
|
return successResponse({ user: registeredUser, message: "Registration successful" }, 201);
|
|
3006
3164
|
}
|
|
3007
|
-
|
|
3165
|
+
const registerResponse = withSession(loginData, { user: registeredUser, sessionToken: loginData.session_token, userId: registeredUser.id }, cookieOptions);
|
|
3166
|
+
if (config.enableAccountSwitcher) {
|
|
3167
|
+
const existingKnown = getKnownAccountsCookieRaw(request);
|
|
3168
|
+
appendKnownAccountCookie(
|
|
3169
|
+
registerResponse.headers,
|
|
3170
|
+
{
|
|
3171
|
+
userId: registeredUser.id,
|
|
3172
|
+
email: registeredUser.email,
|
|
3173
|
+
fullName: registeredUser.full_name ?? void 0,
|
|
3174
|
+
avatarUrl: registeredUser.avatar_url ?? void 0,
|
|
3175
|
+
provider: "email",
|
|
3176
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3177
|
+
},
|
|
3178
|
+
existingKnown,
|
|
3179
|
+
cookieOptions,
|
|
3180
|
+
config.accountSwitcherPrivacy
|
|
3181
|
+
);
|
|
3182
|
+
}
|
|
3183
|
+
return registerResponse;
|
|
3008
3184
|
}
|
|
3009
3185
|
// ==================== Login ====================
|
|
3010
3186
|
case "login": {
|
|
@@ -3035,7 +3211,25 @@ function createAuthRoutes(config = {}) {
|
|
|
3035
3211
|
email: loginData.user.email
|
|
3036
3212
|
});
|
|
3037
3213
|
}
|
|
3038
|
-
|
|
3214
|
+
const loginResponse = withSession(loginData, { user: loginData.user, sessionToken: loginData.session_token, userId: loginData.user.id }, cookieOptions);
|
|
3215
|
+
if (config.enableAccountSwitcher) {
|
|
3216
|
+
const existingKnown = getKnownAccountsCookieRaw(request);
|
|
3217
|
+
appendKnownAccountCookie(
|
|
3218
|
+
loginResponse.headers,
|
|
3219
|
+
{
|
|
3220
|
+
userId: loginData.user.id,
|
|
3221
|
+
email: loginData.user.email,
|
|
3222
|
+
fullName: loginData.user.full_name ?? void 0,
|
|
3223
|
+
avatarUrl: loginData.user.avatar_url ?? void 0,
|
|
3224
|
+
provider: "email",
|
|
3225
|
+
lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3226
|
+
},
|
|
3227
|
+
existingKnown,
|
|
3228
|
+
cookieOptions,
|
|
3229
|
+
config.accountSwitcherPrivacy
|
|
3230
|
+
);
|
|
3231
|
+
}
|
|
3232
|
+
return loginResponse;
|
|
3039
3233
|
}
|
|
3040
3234
|
// ==================== Logout ====================
|
|
3041
3235
|
case "logout": {
|
|
@@ -3182,6 +3376,59 @@ function createAuthRoutes(config = {}) {
|
|
|
3182
3376
|
}
|
|
3183
3377
|
return successResponse({ message: "Password changed successfully" });
|
|
3184
3378
|
}
|
|
3379
|
+
// ==================== Switch Account ====================
|
|
3380
|
+
// Clears the active session so the user can re-authenticate as a different account.
|
|
3381
|
+
// The known accounts cookie is preserved — only the session cookie is cleared.
|
|
3382
|
+
case "switch-account": {
|
|
3383
|
+
if (!config.enableAccountSwitcher) {
|
|
3384
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3385
|
+
}
|
|
3386
|
+
const session = await getSession();
|
|
3387
|
+
if (session) {
|
|
3388
|
+
try {
|
|
3389
|
+
await sm.auth.logout(session.sessionToken);
|
|
3390
|
+
} catch {
|
|
3391
|
+
}
|
|
3392
|
+
}
|
|
3393
|
+
const switchResponse = clearSession({ message: "Session cleared for account switch" }, cookieOptions);
|
|
3394
|
+
const knownAccounts = getKnownAccountsFromRequest(request);
|
|
3395
|
+
return new Response(
|
|
3396
|
+
JSON.stringify({ success: true, data: { message: "Session cleared for account switch", knownAccounts } }),
|
|
3397
|
+
{ status: 200, headers: switchResponse.headers }
|
|
3398
|
+
);
|
|
3399
|
+
}
|
|
3400
|
+
// ==================== Forget Account ====================
|
|
3401
|
+
// Remove a specific account from the known accounts cookie.
|
|
3402
|
+
case "forget-account": {
|
|
3403
|
+
if (!config.enableAccountSwitcher) {
|
|
3404
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3405
|
+
}
|
|
3406
|
+
const { user_id } = body;
|
|
3407
|
+
if (!user_id) {
|
|
3408
|
+
return errorResponse("VALIDATION_ERROR", "user_id required", 400);
|
|
3409
|
+
}
|
|
3410
|
+
const headers2 = new Headers();
|
|
3411
|
+
headers2.set("Content-Type", "application/json");
|
|
3412
|
+
const existingKnown = getKnownAccountsCookieRaw(request);
|
|
3413
|
+
removeKnownAccountFromCookie(headers2, user_id, existingKnown, cookieOptions);
|
|
3414
|
+
return new Response(
|
|
3415
|
+
JSON.stringify({ success: true, data: { message: "Account forgotten" } }),
|
|
3416
|
+
{ status: 200, headers: headers2 }
|
|
3417
|
+
);
|
|
3418
|
+
}
|
|
3419
|
+
// ==================== Forget All Accounts ====================
|
|
3420
|
+
case "forget-all-accounts": {
|
|
3421
|
+
if (!config.enableAccountSwitcher) {
|
|
3422
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3423
|
+
}
|
|
3424
|
+
const headers2 = new Headers();
|
|
3425
|
+
headers2.set("Content-Type", "application/json");
|
|
3426
|
+
clearKnownAccountsCookie(headers2, cookieOptions);
|
|
3427
|
+
return new Response(
|
|
3428
|
+
JSON.stringify({ success: true, data: { message: "All accounts forgotten" } }),
|
|
3429
|
+
{ status: 200, headers: headers2 }
|
|
3430
|
+
);
|
|
3431
|
+
}
|
|
3185
3432
|
default:
|
|
3186
3433
|
return errorResponse("NOT_FOUND", `Unknown endpoint: ${path}`, 404);
|
|
3187
3434
|
}
|
|
@@ -3197,20 +3444,25 @@ function createAuthRoutes(config = {}) {
|
|
|
3197
3444
|
switch (path) {
|
|
3198
3445
|
// ==================== Get Current User ====================
|
|
3199
3446
|
case "me": {
|
|
3447
|
+
const normCookie = config.enableAccountSwitcher ? normalizeKnownAccountsCookie(request, config.accountSwitcherPrivacy, cookieOptions) : null;
|
|
3448
|
+
const withNorm = (resp) => {
|
|
3449
|
+
if (normCookie) resp.headers.append("Set-Cookie", normCookie);
|
|
3450
|
+
return resp;
|
|
3451
|
+
};
|
|
3200
3452
|
const session = await getSession();
|
|
3201
3453
|
if (!session) {
|
|
3202
|
-
return errorResponse("UNAUTHORIZED", "Authentication required", 401);
|
|
3454
|
+
return withNorm(errorResponse("UNAUTHORIZED", "Authentication required", 401));
|
|
3203
3455
|
}
|
|
3204
3456
|
let userData;
|
|
3205
3457
|
try {
|
|
3206
3458
|
userData = await sm.auth.me(session.sessionToken);
|
|
3207
3459
|
} catch {
|
|
3208
|
-
return clearSession(
|
|
3460
|
+
return withNorm(clearSession(
|
|
3209
3461
|
{ error: { code: "SESSION_EXPIRED", message: "Session expired" } },
|
|
3210
3462
|
cookieOptions
|
|
3211
|
-
);
|
|
3463
|
+
));
|
|
3212
3464
|
}
|
|
3213
|
-
return successResponse({ user: userData, sessionToken: session.sessionToken, userId: session.userId });
|
|
3465
|
+
return withNorm(successResponse({ user: userData, sessionToken: session.sessionToken, userId: session.userId }));
|
|
3214
3466
|
}
|
|
3215
3467
|
// ==================== Get Session Status ====================
|
|
3216
3468
|
case "session": {
|
|
@@ -3220,6 +3472,14 @@ function createAuthRoutes(config = {}) {
|
|
|
3220
3472
|
userId: session?.userId || null
|
|
3221
3473
|
});
|
|
3222
3474
|
}
|
|
3475
|
+
// ==================== Get Known Accounts ====================
|
|
3476
|
+
case "known-accounts": {
|
|
3477
|
+
if (!config.enableAccountSwitcher) {
|
|
3478
|
+
return errorResponse("NOT_FOUND", "Account switcher not enabled", 404);
|
|
3479
|
+
}
|
|
3480
|
+
const knownAccounts = getKnownAccountsFromRequest(request);
|
|
3481
|
+
return successResponse({ knownAccounts });
|
|
3482
|
+
}
|
|
3223
3483
|
default:
|
|
3224
3484
|
return errorResponse("NOT_FOUND", `Unknown endpoint: ${path}`, 404);
|
|
3225
3485
|
}
|
|
@@ -4829,14 +5089,17 @@ async function prefetchBundles(keys) {
|
|
|
4829
5089
|
|
|
4830
5090
|
exports.CSRF_COOKIE_NAME = CSRF_COOKIE_NAME;
|
|
4831
5091
|
exports.CSRF_HEADER_NAME = CSRF_HEADER_NAME;
|
|
5092
|
+
exports.KNOWN_ACCOUNTS_COOKIE_NAME = KNOWN_ACCOUNTS_COOKIE_NAME;
|
|
4832
5093
|
exports.OAUTH_STATE_COOKIE_NAME = OAUTH_STATE_COOKIE_NAME;
|
|
4833
5094
|
exports.SESSION_COOKIE_NAME = SESSION_COOKIE_NAME;
|
|
4834
5095
|
exports.ScaleMuleError = ScaleMuleError;
|
|
4835
5096
|
exports.ScaleMuleServer = ScaleMuleServer;
|
|
4836
5097
|
exports.USER_ID_COOKIE_NAME = USER_ID_COOKIE_NAME;
|
|
4837
5098
|
exports.apiHandler = apiHandler;
|
|
5099
|
+
exports.appendKnownAccountCookie = appendKnownAccountCookie;
|
|
4838
5100
|
exports.buildClientContextHeaders = buildClientContextHeaders;
|
|
4839
5101
|
exports.buildFlagContext = buildFlagContext;
|
|
5102
|
+
exports.clearKnownAccountsCookie = clearKnownAccountsCookie;
|
|
4840
5103
|
exports.clearOAuthState = clearOAuthState;
|
|
4841
5104
|
exports.clearSession = clearSession;
|
|
4842
5105
|
exports.configureBundles = configureBundles;
|
|
@@ -4858,6 +5121,8 @@ exports.getAppSecretOrDefault = getAppSecretOrDefault;
|
|
|
4858
5121
|
exports.getBootstrapFlags = getBootstrapFlags;
|
|
4859
5122
|
exports.getBundle = getBundle;
|
|
4860
5123
|
exports.getCSRFToken = getCSRFToken;
|
|
5124
|
+
exports.getKnownAccountsCookieRaw = getKnownAccountsCookieRaw;
|
|
5125
|
+
exports.getKnownAccountsFromRequest = getKnownAccountsFromRequest;
|
|
4861
5126
|
exports.getMySqlBundle = getMySqlBundle;
|
|
4862
5127
|
exports.getOAuthBundle = getOAuthBundle;
|
|
4863
5128
|
exports.getPostgresBundle = getPostgresBundle;
|
|
@@ -4868,10 +5133,12 @@ exports.getSessionFromRequest = getSessionFromRequest;
|
|
|
4868
5133
|
exports.getSmtpBundle = getSmtpBundle;
|
|
4869
5134
|
exports.invalidateBundleCache = invalidateBundleCache;
|
|
4870
5135
|
exports.invalidateSecretCache = invalidateSecretCache;
|
|
5136
|
+
exports.normalizeKnownAccountsCookie = normalizeKnownAccountsCookie;
|
|
4871
5137
|
exports.parseWebhookEvent = parseWebhookEvent;
|
|
4872
5138
|
exports.prefetchBundles = prefetchBundles;
|
|
4873
5139
|
exports.prefetchSecrets = prefetchSecrets;
|
|
4874
5140
|
exports.registerVideoWebhook = registerVideoWebhook;
|
|
5141
|
+
exports.removeKnownAccountFromCookie = removeKnownAccountFromCookie;
|
|
4875
5142
|
exports.requireAppSecret = requireAppSecret;
|
|
4876
5143
|
exports.requireBundle = requireBundle;
|
|
4877
5144
|
exports.requireSession = requireSession;
|