@caffeinebounce/identity 0.12.1 → 0.12.3

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/index.d.mts CHANGED
@@ -241,6 +241,10 @@ interface ConsentItem {
241
241
  errorMessage?: string;
242
242
  }
243
243
  interface SignupFormProps extends AuthFormConfig {
244
+ /** Optional heading override for the sign-up form */
245
+ title?: string;
246
+ /** Optional initial value for the email field */
247
+ initialEmail?: string;
244
248
  /** Navigation links configuration */
245
249
  links?: AuthLinks;
246
250
  /** Image component to use (e.g., Next.js Image) */
@@ -283,7 +287,7 @@ interface SignupFormProps extends AuthFormConfig {
283
287
  /**
284
288
  * SignupForm - Configurable sign-up form with email/password and OAuth support
285
289
  */
286
- declare function SignupForm({ createClient, logo, appName, termsUrl, privacyUrl, links, ImageComponent, LinkComponent, oauthProviders, googleComingSoon, azureComingSoon, oauthIconMonochromeOnHover, className, onAuthEvent, consentItems, consentPosition, consentSize, showHomeLink, showLogo, }: SignupFormProps): react_jsx_runtime.JSX.Element;
290
+ declare function SignupForm({ createClient, logo, appName, title, initialEmail, termsUrl, privacyUrl, links, ImageComponent, LinkComponent, oauthProviders, googleComingSoon, azureComingSoon, oauthIconMonochromeOnHover, className, onAuthEvent, consentItems, consentPosition, consentSize, showHomeLink, showLogo, }: SignupFormProps): react_jsx_runtime.JSX.Element;
287
291
 
288
292
  /**
289
293
  * MFA event logging callbacks
@@ -480,6 +484,8 @@ interface DeleteAccountSectionProps {
480
484
  declare function DeleteAccountSection({ createClient, email, retentionDays, }: DeleteAccountSectionProps): react_jsx_runtime.JSX.Element;
481
485
 
482
486
  interface EmailSectionProps {
487
+ /** Render the change action as a compact pencil icon instead of a text label */
488
+ changeButtonPresentation?: "label" | "icon";
483
489
  /** Function to create a Supabase client */
484
490
  createClient: CreateClientFn;
485
491
  /** User ID (reserved for future use) */
@@ -491,7 +497,7 @@ interface EmailSectionProps {
491
497
  /** Callback when email is changed */
492
498
  onEmailChanged: (newEmail: string) => void;
493
499
  }
494
- declare function EmailSection({ createClient, userId: _userId, email, isVerified, onEmailChanged, }: EmailSectionProps): react_jsx_runtime.JSX.Element;
500
+ declare function EmailSection({ changeButtonPresentation, createClient, userId: _userId, email, isVerified, onEmailChanged, }: EmailSectionProps): react_jsx_runtime.JSX.Element;
495
501
 
496
502
  interface LinkedAccountsSectionProps {
497
503
  /** Function to create a Supabase client */
package/dist/index.d.ts CHANGED
@@ -241,6 +241,10 @@ interface ConsentItem {
241
241
  errorMessage?: string;
242
242
  }
243
243
  interface SignupFormProps extends AuthFormConfig {
244
+ /** Optional heading override for the sign-up form */
245
+ title?: string;
246
+ /** Optional initial value for the email field */
247
+ initialEmail?: string;
244
248
  /** Navigation links configuration */
245
249
  links?: AuthLinks;
246
250
  /** Image component to use (e.g., Next.js Image) */
@@ -283,7 +287,7 @@ interface SignupFormProps extends AuthFormConfig {
283
287
  /**
284
288
  * SignupForm - Configurable sign-up form with email/password and OAuth support
285
289
  */
286
- declare function SignupForm({ createClient, logo, appName, termsUrl, privacyUrl, links, ImageComponent, LinkComponent, oauthProviders, googleComingSoon, azureComingSoon, oauthIconMonochromeOnHover, className, onAuthEvent, consentItems, consentPosition, consentSize, showHomeLink, showLogo, }: SignupFormProps): react_jsx_runtime.JSX.Element;
290
+ declare function SignupForm({ createClient, logo, appName, title, initialEmail, termsUrl, privacyUrl, links, ImageComponent, LinkComponent, oauthProviders, googleComingSoon, azureComingSoon, oauthIconMonochromeOnHover, className, onAuthEvent, consentItems, consentPosition, consentSize, showHomeLink, showLogo, }: SignupFormProps): react_jsx_runtime.JSX.Element;
287
291
 
288
292
  /**
289
293
  * MFA event logging callbacks
@@ -480,6 +484,8 @@ interface DeleteAccountSectionProps {
480
484
  declare function DeleteAccountSection({ createClient, email, retentionDays, }: DeleteAccountSectionProps): react_jsx_runtime.JSX.Element;
481
485
 
482
486
  interface EmailSectionProps {
487
+ /** Render the change action as a compact pencil icon instead of a text label */
488
+ changeButtonPresentation?: "label" | "icon";
483
489
  /** Function to create a Supabase client */
484
490
  createClient: CreateClientFn;
485
491
  /** User ID (reserved for future use) */
@@ -491,7 +497,7 @@ interface EmailSectionProps {
491
497
  /** Callback when email is changed */
492
498
  onEmailChanged: (newEmail: string) => void;
493
499
  }
494
- declare function EmailSection({ createClient, userId: _userId, email, isVerified, onEmailChanged, }: EmailSectionProps): react_jsx_runtime.JSX.Element;
500
+ declare function EmailSection({ changeButtonPresentation, createClient, userId: _userId, email, isVerified, onEmailChanged, }: EmailSectionProps): react_jsx_runtime.JSX.Element;
495
501
 
496
502
  interface LinkedAccountsSectionProps {
497
503
  /** Function to create a Supabase client */
package/dist/index.js CHANGED
@@ -19,6 +19,17 @@ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
19
19
  var isEmail__default = /*#__PURE__*/_interopDefault(isEmail);
20
20
 
21
21
  // src/components/auth/EmailVerificationPending.tsx
22
+
23
+ // src/types.ts
24
+ var defaultAuthLinks = {
25
+ signIn: "/signin",
26
+ signUp: "/signup",
27
+ forgotPassword: "/forgot-password",
28
+ resetPassword: "/reset-password",
29
+ callback: "/callback",
30
+ home: "/",
31
+ defaultRedirect: "/dashboard"
32
+ };
22
33
  function AuthFormLayout({
23
34
  children,
24
35
  footer,
@@ -55,6 +66,122 @@ function AuthFormLayout({
55
66
  ] }) })
56
67
  ] });
57
68
  }
69
+
70
+ // src/utils/redirect.ts
71
+ var INTERNAL_REDIRECT_BASE = new URL("https://identity.invalid");
72
+ var ENCODED_PATH_SEPARATOR = /%(?:2f|5c)/i;
73
+ function hasControlCharacter(value) {
74
+ return Array.from(value).some((character) => {
75
+ const code = character.charCodeAt(0);
76
+ return code <= 31 || code === 127;
77
+ });
78
+ }
79
+ function isStrictInternalRedirect(candidate) {
80
+ if (!candidate?.startsWith("/") || hasControlCharacter(candidate)) {
81
+ return false;
82
+ }
83
+ const pathEnd = candidate.search(/[?#]/);
84
+ const pathname = pathEnd === -1 ? candidate : candidate.slice(0, pathEnd);
85
+ if (pathname.includes("\\") || ENCODED_PATH_SEPARATOR.test(pathname)) {
86
+ return false;
87
+ }
88
+ try {
89
+ const target = new URL(candidate, INTERNAL_REDIRECT_BASE);
90
+ return target.origin === INTERNAL_REDIRECT_BASE.origin;
91
+ } catch {
92
+ return false;
93
+ }
94
+ }
95
+ function getSafeInternalRedirect(candidate, fallback) {
96
+ if (isStrictInternalRedirect(candidate)) {
97
+ return candidate;
98
+ }
99
+ if (fallback === null) {
100
+ return null;
101
+ }
102
+ return isStrictInternalRedirect(fallback) ? fallback : "/";
103
+ }
104
+
105
+ // src/components/auth/utils.ts
106
+ var warnedSupabaseRedirectOrigins = /* @__PURE__ */ new Set();
107
+ function hardRedirect(redirectTo) {
108
+ window.location.href = redirectTo;
109
+ }
110
+ function sanitizeAuthError(message) {
111
+ const lowerMessage = message.toLowerCase();
112
+ if (lowerMessage.includes("user already registered") || lowerMessage.includes("already registered") || lowerMessage.includes("already exists") || lowerMessage.includes("email already") || lowerMessage.includes("duplicate")) {
113
+ return "An account with this email already exists. Please sign in instead.";
114
+ }
115
+ if (lowerMessage.includes("invalid login credentials") || lowerMessage.includes("invalid credentials") || lowerMessage.includes("wrong password") || lowerMessage.includes("incorrect password")) {
116
+ return "Invalid email or password. Please try again.";
117
+ }
118
+ if (lowerMessage.includes("invalid email") || lowerMessage.includes("password")) {
119
+ return message;
120
+ }
121
+ if (message === "The string did not match the expected pattern.") {
122
+ return "Please check your email address format and try again.";
123
+ }
124
+ if (lowerMessage.includes("rate limit") || lowerMessage.includes("too many requests") || lowerMessage.includes("try again later")) {
125
+ return "Too many attempts. Please wait a moment and try again.";
126
+ }
127
+ if (lowerMessage.includes("hook") || lowerMessage.includes("authorization") || lowerMessage.includes("token") || lowerMessage.includes("internal") || lowerMessage.includes("server") || lowerMessage.includes("database") || lowerMessage.includes("connection")) {
128
+ return "An error occurred. Please try again.";
129
+ }
130
+ return "An error occurred. Please try again.";
131
+ }
132
+ function sanitizeSignupError(message) {
133
+ const sanitized = sanitizeAuthError(message);
134
+ if (sanitized === "An error occurred. Please try again.") {
135
+ return "An error occurred during sign up. Please try again.";
136
+ }
137
+ return sanitized;
138
+ }
139
+ function buildOAuthRedirectTo(origin, nextPath) {
140
+ const siteUrl = origin.replace(/\/$/, "");
141
+ return `${siteUrl}/callback?next=${encodeURIComponent(nextPath)}`;
142
+ }
143
+ function buildOAuthSignInOptions(provider, redirectTo, options = {}) {
144
+ return {
145
+ redirectTo,
146
+ ...provider === "azure" ? { scopes: "email" } : {},
147
+ ...options
148
+ };
149
+ }
150
+ function warnAboutSupabaseRedirectAllowlist(origin) {
151
+ const normalizedOrigin = origin.replace(/\/$/, "");
152
+ let hostname = "";
153
+ try {
154
+ hostname = new URL(normalizedOrigin).host;
155
+ } catch {
156
+ return;
157
+ }
158
+ if (!sharedUtils.isPreviewEnvironment(hostname)) {
159
+ return;
160
+ }
161
+ if (warnedSupabaseRedirectOrigins.has(normalizedOrigin)) {
162
+ return;
163
+ }
164
+ warnedSupabaseRedirectOrigins.add(normalizedOrigin);
165
+ console.warn(
166
+ `[identity] Supabase redirect allowlist reminder: add "${normalizedOrigin}/**" to Supabase Auth > URL Configuration > Additional Redirect URLs. The auth components already use the active origin for callbacks, but Supabase must allow that origin too or OAuth/email flows can bounce to another host.`
167
+ );
168
+ }
169
+ function clearStalePKCEState() {
170
+ if (typeof window === "undefined") return;
171
+ try {
172
+ const keysToRemove = [];
173
+ for (let i = 0; i < localStorage.length; i++) {
174
+ const key = localStorage.key(i);
175
+ if (key?.includes("-code-verifier")) {
176
+ keysToRemove.push(key);
177
+ }
178
+ }
179
+ for (const key of keysToRemove) {
180
+ localStorage.removeItem(key);
181
+ }
182
+ } catch {
183
+ }
184
+ }
58
185
  var POLL_INTERVAL_MS = 6e4;
59
186
  var MAX_POLL_COUNT = 60;
60
187
  function EmailVerificationPending({
@@ -125,9 +252,19 @@ function EmailVerificationPending({
125
252
  setResendStatus("sending");
126
253
  try {
127
254
  const supabase = createClient();
255
+ const safeRedirectTo = getSafeInternalRedirect(
256
+ redirectTo,
257
+ defaultAuthLinks.defaultRedirect
258
+ );
128
259
  const { error } = await supabase.auth.resend({
129
260
  type: "signup",
130
- email
261
+ email,
262
+ options: {
263
+ emailRedirectTo: buildOAuthRedirectTo(
264
+ window.location.origin,
265
+ safeRedirectTo
266
+ )
267
+ }
131
268
  });
132
269
  if (error) {
133
270
  setResendStatus("error");
@@ -247,17 +384,6 @@ function EmailVerificationPending({
247
384
  }
248
385
  );
249
386
  }
250
-
251
- // src/types.ts
252
- var defaultAuthLinks = {
253
- signIn: "/signin",
254
- signUp: "/signup",
255
- forgotPassword: "/forgot-password",
256
- resetPassword: "/reset-password",
257
- callback: "/callback",
258
- home: "/",
259
- defaultRedirect: "/dashboard"
260
- };
261
387
  function AuthHeader({
262
388
  logo,
263
389
  title,
@@ -279,82 +405,6 @@ function AuthHeader({
279
405
  /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-semibold tracking-tight text-foreground", children: title })
280
406
  ] });
281
407
  }
282
- var warnedSupabaseRedirectOrigins = /* @__PURE__ */ new Set();
283
- function sanitizeAuthError(message) {
284
- const lowerMessage = message.toLowerCase();
285
- if (lowerMessage.includes("user already registered") || lowerMessage.includes("already registered") || lowerMessage.includes("already exists") || lowerMessage.includes("email already") || lowerMessage.includes("duplicate")) {
286
- return "An account with this email already exists. Please sign in instead.";
287
- }
288
- if (lowerMessage.includes("invalid login credentials") || lowerMessage.includes("invalid credentials") || lowerMessage.includes("wrong password") || lowerMessage.includes("incorrect password")) {
289
- return "Invalid email or password. Please try again.";
290
- }
291
- if (lowerMessage.includes("invalid email") || lowerMessage.includes("password")) {
292
- return message;
293
- }
294
- if (message === "The string did not match the expected pattern.") {
295
- return "Please check your email address format and try again.";
296
- }
297
- if (lowerMessage.includes("rate limit") || lowerMessage.includes("too many requests") || lowerMessage.includes("try again later")) {
298
- return "Too many attempts. Please wait a moment and try again.";
299
- }
300
- if (lowerMessage.includes("hook") || lowerMessage.includes("authorization") || lowerMessage.includes("token") || lowerMessage.includes("internal") || lowerMessage.includes("server") || lowerMessage.includes("database") || lowerMessage.includes("connection")) {
301
- return "An error occurred. Please try again.";
302
- }
303
- return "An error occurred. Please try again.";
304
- }
305
- function sanitizeSignupError(message) {
306
- const sanitized = sanitizeAuthError(message);
307
- if (sanitized === "An error occurred. Please try again.") {
308
- return "An error occurred during sign up. Please try again.";
309
- }
310
- return sanitized;
311
- }
312
- function buildOAuthRedirectTo(origin, nextPath) {
313
- const siteUrl = origin.replace(/\/$/, "");
314
- return `${siteUrl}/callback?next=${encodeURIComponent(nextPath)}`;
315
- }
316
- function buildOAuthSignInOptions(provider, redirectTo, options = {}) {
317
- return {
318
- redirectTo,
319
- ...provider === "azure" ? { scopes: "email" } : {},
320
- ...options
321
- };
322
- }
323
- function warnAboutSupabaseRedirectAllowlist(origin) {
324
- const normalizedOrigin = origin.replace(/\/$/, "");
325
- let hostname = "";
326
- try {
327
- hostname = new URL(normalizedOrigin).host;
328
- } catch {
329
- return;
330
- }
331
- if (!sharedUtils.isPreviewEnvironment(hostname)) {
332
- return;
333
- }
334
- if (warnedSupabaseRedirectOrigins.has(normalizedOrigin)) {
335
- return;
336
- }
337
- warnedSupabaseRedirectOrigins.add(normalizedOrigin);
338
- console.warn(
339
- `[identity] Supabase redirect allowlist reminder: add "${normalizedOrigin}/**" to Supabase Auth > URL Configuration > Additional Redirect URLs. The auth components already use the active origin for callbacks, but Supabase must allow that origin too or OAuth/email flows can bounce to another host.`
340
- );
341
- }
342
- function clearStalePKCEState() {
343
- if (typeof window === "undefined") return;
344
- try {
345
- const keysToRemove = [];
346
- for (let i = 0; i < localStorage.length; i++) {
347
- const key = localStorage.key(i);
348
- if (key?.includes("-code-verifier")) {
349
- keysToRemove.push(key);
350
- }
351
- }
352
- for (const key of keysToRemove) {
353
- localStorage.removeItem(key);
354
- }
355
- } catch {
356
- }
357
- }
358
408
  function ForgotPasswordForm({
359
409
  createClient,
360
410
  logo,
@@ -1095,7 +1145,14 @@ function SigninForm({
1095
1145
  const oauthTimeoutRef = react.useRef(null);
1096
1146
  const router = navigation.useRouter();
1097
1147
  const searchParams = navigation.useSearchParams();
1098
- const redirectTo = searchParams.get("redirect") || mergedLinks.defaultRedirect;
1148
+ const defaultRedirect = getSafeInternalRedirect(
1149
+ mergedLinks.defaultRedirect,
1150
+ defaultAuthLinks.defaultRedirect
1151
+ );
1152
+ const redirectTo = getSafeInternalRedirect(
1153
+ searchParams.get("redirect"),
1154
+ defaultRedirect
1155
+ );
1099
1156
  const message = searchParams.get("message");
1100
1157
  const mfaRequired = searchParams.get("mfa") === "required";
1101
1158
  const [showMFA, setShowMFA] = react.useState(mfaRequired);
@@ -1267,7 +1324,7 @@ function SigninForm({
1267
1324
  } else {
1268
1325
  onAuthEvent?.onSignInSuccess?.(userId, email.trim(), false);
1269
1326
  recordSignIn("email", email.trim());
1270
- window.location.href = redirectTo;
1327
+ hardRedirect(redirectTo);
1271
1328
  }
1272
1329
  } catch (err) {
1273
1330
  logError(err, {
@@ -1427,10 +1484,13 @@ function SigninForm({
1427
1484
  }
1428
1485
  );
1429
1486
  }
1487
+ var EMPTY_CONSENT_ITEMS = [];
1430
1488
  function SignupForm({
1431
1489
  createClient,
1432
1490
  logo,
1433
1491
  appName = "your account",
1492
+ title,
1493
+ initialEmail = "",
1434
1494
  termsUrl = "/terms",
1435
1495
  privacyUrl = "/privacy",
1436
1496
  links = {},
@@ -1442,7 +1502,7 @@ function SignupForm({
1442
1502
  oauthIconMonochromeOnHover = false,
1443
1503
  className,
1444
1504
  onAuthEvent,
1445
- consentItems = [],
1505
+ consentItems = EMPTY_CONSENT_ITEMS,
1446
1506
  consentPosition = "above",
1447
1507
  consentSize = "default",
1448
1508
  showHomeLink = true,
@@ -1452,7 +1512,11 @@ function SignupForm({
1452
1512
  const Link = LinkComponent;
1453
1513
  const Image = ImageComponent;
1454
1514
  const router = navigation.useRouter();
1455
- const [email, setEmail] = react.useState("");
1515
+ const redirectTo = getSafeInternalRedirect(
1516
+ mergedLinks.defaultRedirect,
1517
+ defaultAuthLinks.defaultRedirect
1518
+ );
1519
+ const [email, setEmail] = react.useState(initialEmail);
1456
1520
  const [emailTouched, setEmailTouched] = react.useState(false);
1457
1521
  const [password, setPassword] = react.useState("");
1458
1522
  const [confirmPassword, setConfirmPassword] = react.useState("");
@@ -1517,7 +1581,7 @@ function SignupForm({
1517
1581
  provider,
1518
1582
  options: buildOAuthSignInOptions(
1519
1583
  provider,
1520
- buildOAuthRedirectTo(siteUrl, mergedLinks.defaultRedirect)
1584
+ buildOAuthRedirectTo(siteUrl, redirectTo)
1521
1585
  )
1522
1586
  });
1523
1587
  if (error) {
@@ -1552,7 +1616,7 @@ function SignupForm({
1552
1616
  email: email.trim(),
1553
1617
  password,
1554
1618
  options: {
1555
- emailRedirectTo: `${siteUrl}/callback`,
1619
+ emailRedirectTo: buildOAuthRedirectTo(siteUrl, redirectTo),
1556
1620
  // Pass all consent values as user metadata
1557
1621
  data: consentState
1558
1622
  }
@@ -1570,7 +1634,7 @@ function SignupForm({
1570
1634
  } else if (data.user) {
1571
1635
  const userId = data.user.id;
1572
1636
  onAuthEvent?.onSignUpSuccess?.(userId, email.trim());
1573
- router.push(mergedLinks.defaultRedirect);
1637
+ router.push(redirectTo);
1574
1638
  router.refresh();
1575
1639
  } else {
1576
1640
  onAuthEvent?.onSignUpFailure?.(email.trim(), "no_user_data_in_response");
@@ -1586,7 +1650,7 @@ function SignupForm({
1586
1650
  logo,
1587
1651
  ImageComponent: Image,
1588
1652
  createClient,
1589
- redirectTo: mergedLinks.home,
1653
+ redirectTo,
1590
1654
  className
1591
1655
  }
1592
1656
  );
@@ -1608,7 +1672,7 @@ function SignupForm({
1608
1672
  AuthHeader,
1609
1673
  {
1610
1674
  logo: showLogo !== false ? logo : void 0,
1611
- title: `Sign up for ${appName}`,
1675
+ title: title ?? `Sign up for ${appName}`,
1612
1676
  ImageComponent: Image
1613
1677
  }
1614
1678
  ),
@@ -3722,6 +3786,7 @@ function useVerificationFlow({
3722
3786
  };
3723
3787
  }
3724
3788
  function EmailSection({
3789
+ changeButtonPresentation = "label",
3725
3790
  createClient,
3726
3791
  userId: _userId,
3727
3792
  email,
@@ -3749,6 +3814,26 @@ function EmailSection({
3749
3814
  const handleStartChange = () => {
3750
3815
  openEntryStep();
3751
3816
  };
3817
+ const changeButton = changeButtonPresentation === "icon" ? /* @__PURE__ */ jsxRuntime.jsx(
3818
+ primitives.Button,
3819
+ {
3820
+ "aria-label": "Change email",
3821
+ onClick: handleStartChange,
3822
+ size: "icon-sm",
3823
+ type: "button",
3824
+ variant: "outline",
3825
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PencilLine, { "aria-hidden": "true", className: "size-4" })
3826
+ }
3827
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
3828
+ primitives.Button,
3829
+ {
3830
+ onClick: handleStartChange,
3831
+ size: "sm",
3832
+ type: "button",
3833
+ variant: "outline",
3834
+ children: "Change"
3835
+ }
3836
+ );
3752
3837
  const handleSendCode = async () => {
3753
3838
  if (!newEmail || !newEmail.includes("@")) {
3754
3839
  setError("Please enter a valid email address");
@@ -3828,16 +3913,10 @@ function EmailSection({
3828
3913
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: email })
3829
3914
  ] })
3830
3915
  ] }),
3831
- /* @__PURE__ */ jsxRuntime.jsx(
3832
- primitives.Button,
3833
- {
3834
- type: "button",
3835
- variant: "outline",
3836
- size: "sm",
3837
- onClick: handleStartChange,
3838
- children: "Change"
3839
- }
3840
- ),
3916
+ changeButtonPresentation === "icon" ? /* @__PURE__ */ jsxRuntime.jsx(primitives.TooltipProvider, { delayDuration: 300, children: /* @__PURE__ */ jsxRuntime.jsxs(primitives.Tooltip, { children: [
3917
+ /* @__PURE__ */ jsxRuntime.jsx(primitives.TooltipTrigger, { asChild: true, children: changeButton }),
3918
+ /* @__PURE__ */ jsxRuntime.jsx(primitives.TooltipContent, { side: "top", children: "Change email" })
3919
+ ] }) }) : changeButton,
3841
3920
  /* @__PURE__ */ jsxRuntime.jsx(primitives.Dialog, { open: dialogOpen, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(primitives.DialogContent, { className: "sm:max-w-md", children: [
3842
3921
  step === "enter-email" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3843
3922
  /* @__PURE__ */ jsxRuntime.jsxs(primitives.DialogHeader, { children: [
@@ -5535,12 +5614,6 @@ var EMAIL_OTP_TYPES = [
5535
5614
  "email_change",
5536
5615
  "email"
5537
5616
  ];
5538
- function getSafeRedirectPath(candidate, fallback) {
5539
- if (candidate?.startsWith("/") && !candidate.startsWith("//")) {
5540
- return candidate;
5541
- }
5542
- return fallback;
5543
- }
5544
5617
  function getEmailOtpType(value) {
5545
5618
  if (!value) {
5546
5619
  return null;
@@ -5631,8 +5704,9 @@ function createRedirectResponse(target, origin) {
5631
5704
  if (target instanceof URL) {
5632
5705
  return server.NextResponse.redirect(target.toString());
5633
5706
  }
5634
- if (target.startsWith("/") && !target.startsWith("//")) {
5635
- return server.NextResponse.redirect(`${origin}${target}`);
5707
+ const safeTarget = getSafeInternalRedirect(target, null);
5708
+ if (safeTarget) {
5709
+ return server.NextResponse.redirect(`${origin}${safeTarget}`);
5636
5710
  }
5637
5711
  return null;
5638
5712
  }
@@ -5694,15 +5768,20 @@ function createAuthCallbackHandler({
5694
5768
  isLinkingFlow = isDefaultLinkingFlow,
5695
5769
  resolveLinkingErrorMessage
5696
5770
  }) {
5771
+ const safeDefaultRedirect = getSafeInternalRedirect(
5772
+ defaultRedirect,
5773
+ "/dashboard"
5774
+ );
5775
+ const safeSignInPath = getSafeInternalRedirect(signInPath, "/signin");
5697
5776
  return async function GET(request) {
5698
5777
  const requestUrl = new URL(request.url);
5699
5778
  const code = requestUrl.searchParams.get("code");
5700
5779
  const tokenHash = requestUrl.searchParams.get("token_hash");
5701
5780
  const rawOtpType = requestUrl.searchParams.get("type");
5702
5781
  const otpType = getEmailOtpType(rawOtpType);
5703
- const next = getSafeRedirectPath(
5782
+ const next = getSafeInternalRedirect(
5704
5783
  requestUrl.searchParams.get("next") ?? requestUrl.searchParams.get("redirect_to"),
5705
- defaultRedirect
5784
+ safeDefaultRedirect
5706
5785
  );
5707
5786
  const origin = requestUrl.origin;
5708
5787
  const error = requestUrl.searchParams.get("error");
@@ -5724,7 +5803,7 @@ function createAuthCallbackHandler({
5724
5803
  return linkingErrorRedirect;
5725
5804
  }
5726
5805
  return server.NextResponse.redirect(
5727
- `${origin}${signInPath}?error=${encodeURIComponent(message)}`
5806
+ `${origin}${safeSignInPath}?error=${encodeURIComponent(message)}`
5728
5807
  );
5729
5808
  }
5730
5809
  if (code) {
@@ -5739,7 +5818,7 @@ function createAuthCallbackHandler({
5739
5818
  flow: "oauth",
5740
5819
  postAuthHook,
5741
5820
  postAuthHookErrorMode,
5742
- signInPath,
5821
+ signInPath: safeSignInPath,
5743
5822
  resolveSuccessRedirect
5744
5823
  });
5745
5824
  }
@@ -5759,13 +5838,13 @@ function createAuthCallbackHandler({
5759
5838
  return linkingErrorRedirect;
5760
5839
  }
5761
5840
  return server.NextResponse.redirect(
5762
- `${origin}${signInPath}?error=${encodeURIComponent(errorMessage)}`
5841
+ `${origin}${safeSignInPath}?error=${encodeURIComponent(errorMessage)}`
5763
5842
  );
5764
5843
  }
5765
5844
  if (tokenHash || rawOtpType) {
5766
5845
  if (!tokenHash || !otpType) {
5767
5846
  return server.NextResponse.redirect(
5768
- `${origin}${signInPath}?error=${encodeURIComponent("Invalid verification link")}`
5847
+ `${origin}${safeSignInPath}?error=${encodeURIComponent("Invalid verification link")}`
5769
5848
  );
5770
5849
  }
5771
5850
  const supabase = await createClient();
@@ -5783,16 +5862,16 @@ function createAuthCallbackHandler({
5783
5862
  otpType,
5784
5863
  postAuthHook,
5785
5864
  postAuthHookErrorMode,
5786
- signInPath,
5865
+ signInPath: safeSignInPath,
5787
5866
  resolveSuccessRedirect
5788
5867
  });
5789
5868
  }
5790
5869
  return server.NextResponse.redirect(
5791
- `${origin}${signInPath}?error=${encodeURIComponent(verifyError.message)}`
5870
+ `${origin}${safeSignInPath}?error=${encodeURIComponent(verifyError.message)}`
5792
5871
  );
5793
5872
  }
5794
5873
  return server.NextResponse.redirect(
5795
- `${origin}${signInPath}?error=No authorization code received`
5874
+ `${origin}${safeSignInPath}?error=No authorization code received`
5796
5875
  );
5797
5876
  };
5798
5877
  }