@authagonal/login 0.3.8 → 0.3.9

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/api.d.ts CHANGED
@@ -10,10 +10,10 @@ export declare function register(email: string, password: string, firstName?: st
10
10
  export declare function logout(): Promise<{
11
11
  success: true;
12
12
  }>;
13
- export declare function forgotPassword(email: string): Promise<{
13
+ export declare function forgotPassword(email: string, turnstileToken?: string): Promise<{
14
14
  success: true;
15
15
  }>;
16
- export declare function resetPassword(token: string, newPassword: string): Promise<{
16
+ export declare function resetPassword(token: string, newPassword: string, turnstileToken?: string): Promise<{
17
17
  success: true;
18
18
  }>;
19
19
  export declare function getSession(): Promise<SessionResponse>;
package/dist/index.js CHANGED
@@ -4724,18 +4724,22 @@ function Yr(e, t, n, r, i) {
4724
4724
  function Xr() {
4725
4725
  return $("/api/auth/logout", { method: "POST" });
4726
4726
  }
4727
- function Zr(e) {
4727
+ function Zr(e, t) {
4728
4728
  return $("/api/auth/forgot-password", {
4729
4729
  method: "POST",
4730
- body: JSON.stringify({ email: e })
4730
+ body: JSON.stringify({
4731
+ email: e,
4732
+ turnstileToken: t
4733
+ })
4731
4734
  });
4732
4735
  }
4733
- function Qr(e, t) {
4736
+ function Qr(e, t, n) {
4734
4737
  return $("/api/auth/reset-password", {
4735
4738
  method: "POST",
4736
4739
  body: JSON.stringify({
4737
4740
  token: e,
4738
- newPassword: t
4741
+ newPassword: t,
4742
+ turnstileToken: n
4739
4743
  })
4740
4744
  });
4741
4745
  }
@@ -5326,25 +5330,29 @@ function vi() {
5326
5330
  //#endregion
5327
5331
  //#region src/pages/ForgotPasswordPage.tsx
5328
5332
  function yi() {
5329
- let { t: e } = L(), [t] = _(), n = t.get("returnUrl") || "", [r, i] = s(""), [a, o] = s(!1), [c, d] = s(!1), [p, m] = s(""), h = n ? `/login?returnUrl=${encodeURIComponent(n)}` : "/login";
5330
- async function g(t) {
5331
- t.preventDefault(), m(""), o(!0);
5333
+ let { t: e } = L(), [t] = _(), n = t.get("returnUrl") || "", [r, i] = s(""), [o, c] = s(!1), [d, p] = s(!1), [m, h] = s(""), [g, v] = s(void 0), [y, b] = s(null), [x, S] = s(0);
5334
+ a(() => {
5335
+ ti().then((e) => v(e.turnstileSiteKey)).catch(() => {});
5336
+ }, []);
5337
+ let C = n ? `/login?returnUrl=${encodeURIComponent(n)}` : "/login";
5338
+ async function w(t) {
5339
+ t.preventDefault(), h(""), c(!0);
5332
5340
  try {
5333
- await Zr(r), d(!0);
5341
+ await Zr(r, y || void 0), p(!0);
5334
5342
  } catch {
5335
- m(e("errorUnexpected"));
5343
+ h(e("errorUnexpected")), g && (b(null), S((e) => e + 1));
5336
5344
  } finally {
5337
- o(!1);
5345
+ c(!1);
5338
5346
  }
5339
5347
  }
5340
- return c ? /* @__PURE__ */ u("div", { children: [
5348
+ return d ? /* @__PURE__ */ u("div", { children: [
5341
5349
  /* @__PURE__ */ l(G, { children: e("checkYourEmail") }),
5342
5350
  /* @__PURE__ */ l(Q, {
5343
5351
  variant: "success",
5344
5352
  children: e("resetEmailSent")
5345
5353
  }),
5346
5354
  /* @__PURE__ */ l(K, { children: /* @__PURE__ */ l(f, {
5347
- to: h,
5355
+ to: C,
5348
5356
  className: "text-sm font-medium text-primary hover:underline no-underline",
5349
5357
  children: e("backToSignIn")
5350
5358
  }) })
@@ -5354,12 +5362,12 @@ function yi() {
5354
5362
  className: "mb-5",
5355
5363
  children: e("resetSubtitle")
5356
5364
  }),
5357
- p && /* @__PURE__ */ l(Q, {
5365
+ m && /* @__PURE__ */ l(Q, {
5358
5366
  variant: "error",
5359
- children: p
5367
+ children: m
5360
5368
  }),
5361
5369
  /* @__PURE__ */ u("form", {
5362
- onSubmit: g,
5370
+ onSubmit: w,
5363
5371
  children: [
5364
5372
  /* @__PURE__ */ u("div", {
5365
5373
  className: "mb-4",
@@ -5378,13 +5386,21 @@ function yi() {
5378
5386
  required: !0
5379
5387
  })]
5380
5388
  }),
5389
+ g && /* @__PURE__ */ l("div", {
5390
+ className: "mb-4",
5391
+ children: /* @__PURE__ */ l(mi, {
5392
+ siteKey: g,
5393
+ onToken: b
5394
+ }, x)
5395
+ }),
5381
5396
  /* @__PURE__ */ l(Y, {
5382
5397
  type: "submit",
5383
- loading: a,
5384
- children: e(a ? "sending" : "sendResetLink")
5398
+ loading: o,
5399
+ disabled: !!g && !y,
5400
+ children: e(o ? "sending" : "sendResetLink")
5385
5401
  }),
5386
5402
  /* @__PURE__ */ l(K, { children: /* @__PURE__ */ l(f, {
5387
- to: h,
5403
+ to: C,
5388
5404
  className: "text-sm font-medium text-primary hover:underline no-underline",
5389
5405
  children: e("backToSignIn")
5390
5406
  }) })
@@ -5449,13 +5465,15 @@ function Si(e, t) {
5449
5465
  });
5450
5466
  }
5451
5467
  function Ci() {
5452
- let { t: e } = L(), [t] = _(), n = t.get("p") || "", [r, i] = s(""), [o, c] = s(""), [d, p] = s(!1), [m, h] = s(""), [g, v] = s(!1), [y, b] = s(""), [x, S] = s(xi);
5468
+ let { t: e } = L(), [t] = _(), n = t.get("p") || "", [r, i] = s(""), [o, c] = s(""), [d, p] = s(!1), [m, h] = s(""), [g, v] = s(!1), [y, b] = s(""), [x, S] = s(xi), [C, w] = s(void 0), [T, E] = s(null), [D, O] = s(0);
5453
5469
  a(() => {
5454
5470
  fetch(`${bi}/api/auth/password-policy`).then((e) => e.ok ? e.json() : null).then((e) => {
5455
5471
  e?.rules && S(e.rules);
5456
5472
  }).catch(() => {});
5473
+ }, []), a(() => {
5474
+ ti().then((e) => w(e.turnstileSiteKey)).catch(() => {});
5457
5475
  }, []);
5458
- function C(t) {
5476
+ function k(t) {
5459
5477
  switch (t.rule) {
5460
5478
  case "minLength": return e("ruleMinLength", { count: t.value ?? 8 });
5461
5479
  case "uppercase": return e("ruleUppercase");
@@ -5465,12 +5483,12 @@ function Ci() {
5465
5483
  default: return t.label;
5466
5484
  }
5467
5485
  }
5468
- let w = Si(r, x.map((e) => ({
5486
+ let A = Si(r, x.map((e) => ({
5469
5487
  ...e,
5470
- label: C(e)
5471
- }))), T = w.every((e) => e.met);
5472
- async function E(t) {
5473
- if (t.preventDefault(), h(""), b(""), !T) {
5488
+ label: k(e)
5489
+ }))), j = A.every((e) => e.met);
5490
+ async function M(t) {
5491
+ if (t.preventDefault(), h(""), b(""), !j) {
5474
5492
  b(e("passwordNotMeetRequirements"));
5475
5493
  return;
5476
5494
  }
@@ -5480,7 +5498,7 @@ function Ci() {
5480
5498
  }
5481
5499
  p(!0);
5482
5500
  try {
5483
- await Qr(n, r), v(!0);
5501
+ await Qr(n, r, T || void 0), v(!0);
5484
5502
  } catch (t) {
5485
5503
  if (t instanceof Kr) switch (t.error) {
5486
5504
  case "weak_password":
@@ -5493,9 +5511,13 @@ function Ci() {
5493
5511
  case "password_required":
5494
5512
  h(e("errorPasswordRequired"));
5495
5513
  break;
5514
+ case "captcha_failed":
5515
+ h(e("errorUnexpected"));
5516
+ break;
5496
5517
  default: h(t.message || e("errorUnexpected"));
5497
5518
  }
5498
5519
  else h(e("errorUnexpected"));
5520
+ C && (E(null), O((e) => e + 1));
5499
5521
  } finally {
5500
5522
  p(!1);
5501
5523
  }
@@ -5522,7 +5544,7 @@ function Ci() {
5522
5544
  children: y
5523
5545
  }),
5524
5546
  /* @__PURE__ */ u("form", {
5525
- onSubmit: E,
5547
+ onSubmit: M,
5526
5548
  children: [
5527
5549
  /* @__PURE__ */ u("div", {
5528
5550
  className: "mb-4",
@@ -5543,7 +5565,7 @@ function Ci() {
5543
5565
  }),
5544
5566
  r.length > 0 && /* @__PURE__ */ l("ul", {
5545
5567
  className: "list-none mb-4 p-3 bg-gray-50 dark:bg-gray-800/60 rounded-md",
5546
- children: w.map((e) => /* @__PURE__ */ u("li", {
5568
+ children: A.map((e) => /* @__PURE__ */ u("li", {
5547
5569
  className: `text-[13px] py-0.5 flex items-center gap-1.5 ${e.met ? "text-green-800 dark:text-green-400" : "text-red-800 dark:text-red-400"}`,
5548
5570
  children: [e.met ? /* @__PURE__ */ l(Xt, { className: "h-3.5 w-3.5 shrink-0" }) : /* @__PURE__ */ l(tn, { className: "h-3.5 w-3.5 shrink-0" }), e.label]
5549
5571
  }, e.label))
@@ -5564,10 +5586,17 @@ function Ci() {
5564
5586
  required: !0
5565
5587
  })]
5566
5588
  }),
5589
+ C && /* @__PURE__ */ l("div", {
5590
+ className: "mb-4",
5591
+ children: /* @__PURE__ */ l(mi, {
5592
+ siteKey: C,
5593
+ onToken: E
5594
+ }, D)
5595
+ }),
5567
5596
  /* @__PURE__ */ l(Y, {
5568
5597
  type: "submit",
5569
5598
  loading: d,
5570
- disabled: !T,
5599
+ disabled: !j || !!C && !T,
5571
5600
  children: e(d ? "resetting" : "resetPassword")
5572
5601
  }),
5573
5602
  /* @__PURE__ */ l(K, { children: /* @__PURE__ */ l(f, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@authagonal/login",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Default login UI for Authagonal — runtime-configurable via branding.json",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/api.ts CHANGED
@@ -66,17 +66,17 @@ export function logout(): Promise<{ success: true }> {
66
66
  });
67
67
  }
68
68
 
69
- export function forgotPassword(email: string): Promise<{ success: true }> {
69
+ export function forgotPassword(email: string, turnstileToken?: string): Promise<{ success: true }> {
70
70
  return api<{ success: true }>('/api/auth/forgot-password', {
71
71
  method: 'POST',
72
- body: JSON.stringify({ email }),
72
+ body: JSON.stringify({ email, turnstileToken }),
73
73
  });
74
74
  }
75
75
 
76
- export function resetPassword(token: string, newPassword: string): Promise<{ success: true }> {
76
+ export function resetPassword(token: string, newPassword: string, turnstileToken?: string): Promise<{ success: true }> {
77
77
  return api<{ success: true }>('/api/auth/reset-password', {
78
78
  method: 'POST',
79
- body: JSON.stringify({ token, newPassword }),
79
+ body: JSON.stringify({ token, newPassword, turnstileToken }),
80
80
  });
81
81
  }
82
82
 
@@ -1,7 +1,8 @@
1
- import { useState } from 'react';
1
+ import { useState, useEffect } from 'react';
2
2
  import { useSearchParams, Link } from 'react-router-dom';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { forgotPassword } from '../api';
4
+ import { forgotPassword, getProviders } from '../api';
5
+ import { Turnstile } from '../components/Turnstile';
5
6
  import { Button } from '@/components/ui/button';
6
7
  import { Input } from '@/components/ui/input';
7
8
  import { Label } from '@/components/ui/label';
@@ -17,6 +18,16 @@ export default function ForgotPasswordPage() {
17
18
  const [loading, setLoading] = useState(false);
18
19
  const [submitted, setSubmitted] = useState(false);
19
20
  const [error, setError] = useState('');
21
+ const [turnstileSiteKey, setTurnstileSiteKey] = useState<string | undefined>(undefined);
22
+ const [turnstileToken, setTurnstileToken] = useState<string | null>(null);
23
+ const [turnstileKey, setTurnstileKey] = useState(0); // bump to re-mount the widget for a fresh challenge
24
+
25
+ // Surface the Turnstile site key (opt-in; empty when not configured for the tenant).
26
+ useEffect(() => {
27
+ getProviders()
28
+ .then((res) => setTurnstileSiteKey(res.turnstileSiteKey))
29
+ .catch(() => {});
30
+ }, []);
20
31
 
21
32
  const loginLink = returnUrl
22
33
  ? `/login?returnUrl=${encodeURIComponent(returnUrl)}`
@@ -28,11 +39,16 @@ export default function ForgotPasswordPage() {
28
39
  setLoading(true);
29
40
 
30
41
  try {
31
- await forgotPassword(email);
42
+ await forgotPassword(email, turnstileToken || undefined);
32
43
  setSubmitted(true);
33
44
  } catch {
34
45
  // The API always returns 200 for anti-enumeration, but handle errors just in case
35
46
  setError(t('errorUnexpected'));
47
+ // Turnstile tokens are single-use — reset so a retry gets a fresh challenge.
48
+ if (turnstileSiteKey) {
49
+ setTurnstileToken(null);
50
+ setTurnstileKey((k) => k + 1);
51
+ }
36
52
  } finally {
37
53
  setLoading(false);
38
54
  }
@@ -75,7 +91,13 @@ export default function ForgotPasswordPage() {
75
91
  />
76
92
  </div>
77
93
 
78
- <Button type="submit" loading={loading}>
94
+ {turnstileSiteKey && (
95
+ <div className="mb-4">
96
+ <Turnstile key={turnstileKey} siteKey={turnstileSiteKey} onToken={setTurnstileToken} />
97
+ </div>
98
+ )}
99
+
100
+ <Button type="submit" loading={loading} disabled={!!turnstileSiteKey && !turnstileToken}>
79
101
  {loading ? t('sending') : t('sendResetLink')}
80
102
  </Button>
81
103
 
@@ -1,7 +1,8 @@
1
1
  import { useState, useEffect } from 'react';
2
2
  import { useSearchParams, Link } from 'react-router-dom';
3
3
  import { useTranslation } from 'react-i18next';
4
- import { resetPassword, ApiRequestError } from '../api';
4
+ import { resetPassword, getProviders, ApiRequestError } from '../api';
5
+ import { Turnstile } from '../components/Turnstile';
5
6
  import { Button } from '@/components/ui/button';
6
7
  import { Input } from '@/components/ui/input';
7
8
  import { Label } from '@/components/ui/label';
@@ -57,6 +58,9 @@ export default function ResetPasswordPage() {
57
58
  const [success, setSuccess] = useState(false);
58
59
  const [validationError, setValidationError] = useState('');
59
60
  const [rules, setRules] = useState<PasswordRule[]>(defaultRules);
61
+ const [turnstileSiteKey, setTurnstileSiteKey] = useState<string | undefined>(undefined);
62
+ const [turnstileToken, setTurnstileToken] = useState<string | null>(null);
63
+ const [turnstileKey, setTurnstileKey] = useState(0); // bump to re-mount the widget for a fresh challenge
60
64
 
61
65
  useEffect(() => {
62
66
  fetch(`${API_URL}/api/auth/password-policy`)
@@ -65,6 +69,13 @@ export default function ResetPasswordPage() {
65
69
  .catch(() => { /* use defaults */ });
66
70
  }, []);
67
71
 
72
+ // Surface the Turnstile site key (opt-in; empty when not configured for the tenant).
73
+ useEffect(() => {
74
+ getProviders()
75
+ .then((res) => setTurnstileSiteKey(res.turnstileSiteKey))
76
+ .catch(() => {});
77
+ }, []);
78
+
68
79
  function getRuleLabel(rule: PasswordRule): string {
69
80
  switch (rule.rule) {
70
81
  case 'minLength': return t('ruleMinLength', { count: rule.value ?? 8 });
@@ -102,7 +113,7 @@ export default function ResetPasswordPage() {
102
113
  setLoading(true);
103
114
 
104
115
  try {
105
- await resetPassword(token, newPassword);
116
+ await resetPassword(token, newPassword, turnstileToken || undefined);
106
117
  setSuccess(true);
107
118
  } catch (err) {
108
119
  if (err instanceof ApiRequestError) {
@@ -117,12 +128,20 @@ export default function ResetPasswordPage() {
117
128
  case 'password_required':
118
129
  setError(t('errorPasswordRequired'));
119
130
  break;
131
+ case 'captcha_failed':
132
+ setError(t('errorUnexpected'));
133
+ break;
120
134
  default:
121
135
  setError(err.message || t('errorUnexpected'));
122
136
  }
123
137
  } else {
124
138
  setError(t('errorUnexpected'));
125
139
  }
140
+ // Turnstile tokens are single-use — reset so a retry gets a fresh challenge.
141
+ if (turnstileSiteKey) {
142
+ setTurnstileToken(null);
143
+ setTurnstileKey((k) => k + 1);
144
+ }
126
145
  } finally {
127
146
  setLoading(false);
128
147
  }
@@ -204,7 +223,13 @@ export default function ResetPasswordPage() {
204
223
  />
205
224
  </div>
206
225
 
207
- <Button type="submit" loading={loading} disabled={!allRequirementsMet}>
226
+ {turnstileSiteKey && (
227
+ <div className="mb-4">
228
+ <Turnstile key={turnstileKey} siteKey={turnstileSiteKey} onToken={setTurnstileToken} />
229
+ </div>
230
+ )}
231
+
232
+ <Button type="submit" loading={loading} disabled={!allRequirementsMet || (!!turnstileSiteKey && !turnstileToken)}>
208
233
  {loading ? t('resetting') : t('resetPassword')}
209
234
  </Button>
210
235