@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.
@@ -1,6 +1,6 @@
1
- import { S as ServerConfig } from '../webhook-handler-Cz9jtet2.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-Cz9jtet2.mjs';
3
- import { p as ClientContext, A as ApiError } from '../index-BoENfro3.mjs';
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 };
@@ -1,6 +1,6 @@
1
- import { S as ServerConfig } from '../webhook-handler-CjSSCdvI.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-CjSSCdvI.js';
3
- import { p as ClientContext, A as ApiError } from '../index-BoENfro3.js';
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 };
@@ -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
- return withSession(loginData, { user: registeredUser, sessionToken: loginData.session_token, userId: registeredUser.id }, cookieOptions);
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
- return withSession(loginData, { user: loginData.user, sessionToken: loginData.session_token, userId: loginData.user.id }, cookieOptions);
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;