@kendevelops/auth-flow-kit 1.2.1 → 1.2.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/README.md +239 -239
- package/dist/index.cjs +223 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +223 -49
- package/dist/index.js.map +1 -1
- package/package.json +45 -45
package/dist/index.js
CHANGED
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/http.ts
|
|
11
11
|
function makeURL(baseURL, path) {
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
return `${
|
|
12
|
+
const normalizedBase = baseURL.replace(/\/$/, "");
|
|
13
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
14
|
+
return `${normalizedBase}${normalizedPath}`;
|
|
15
15
|
}
|
|
16
16
|
function getStoredAccessToken() {
|
|
17
17
|
try {
|
|
@@ -22,65 +22,65 @@ function getStoredAccessToken() {
|
|
|
22
22
|
}
|
|
23
23
|
function setStoredAccessToken(token) {
|
|
24
24
|
try {
|
|
25
|
-
if (token
|
|
26
|
-
localStorage.
|
|
25
|
+
if (token !== null) {
|
|
26
|
+
localStorage.setItem("afk_access_token", token);
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
|
-
localStorage.
|
|
29
|
+
localStorage.removeItem("afk_access_token");
|
|
30
30
|
} catch {
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
async function httpJSON(url, opts = {}, withAuth = false) {
|
|
34
|
-
const
|
|
34
|
+
const headers = {
|
|
35
35
|
"Content-Type": "application/json"
|
|
36
36
|
};
|
|
37
|
-
if (withAuth) {
|
|
38
|
-
const
|
|
39
|
-
if (
|
|
40
|
-
|
|
37
|
+
if (withAuth === true) {
|
|
38
|
+
const token = getStoredAccessToken();
|
|
39
|
+
if (token) {
|
|
40
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
const response = await fetch(url, {
|
|
44
44
|
...opts,
|
|
45
45
|
headers: {
|
|
46
|
-
...
|
|
46
|
+
...headers,
|
|
47
47
|
...opts.headers || {}
|
|
48
48
|
}
|
|
49
49
|
});
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
if (responseType.includes("text/html")) {
|
|
63
|
-
if (response.status === 404 && url.includes("forgot")) {
|
|
64
|
-
errorMessage = "The forgot password endpoint you added in config.endpoints.forgot does not exist in your server. Please check and update your config.endpoints.forgot";
|
|
65
|
-
} else {
|
|
66
|
-
errorMessage = "Unexpected server error";
|
|
50
|
+
if (response.ok) {
|
|
51
|
+
return response.json();
|
|
52
|
+
}
|
|
53
|
+
let message = `Request failed (${response.status})`;
|
|
54
|
+
const contentType = response.headers.get("content-type") || "";
|
|
55
|
+
if (contentType.includes("application/json")) {
|
|
56
|
+
try {
|
|
57
|
+
const data = await response.json();
|
|
58
|
+
if (data?.message) {
|
|
59
|
+
message = data.message;
|
|
67
60
|
}
|
|
61
|
+
} catch {
|
|
68
62
|
}
|
|
63
|
+
}
|
|
64
|
+
if (contentType.includes("text/html")) {
|
|
69
65
|
if (response.status === 404 && url.includes("forgot")) {
|
|
70
|
-
|
|
71
|
-
|
|
66
|
+
message = "The forgot password endpoint you added in config.endpoints.forgot does not exist in your server. Please check and update your config.endpoints.forgot";
|
|
67
|
+
} else {
|
|
68
|
+
message = "Unexpected server error";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (response.status === 404 && url.includes("forgot")) {
|
|
72
|
+
console.error(
|
|
73
|
+
`[auth-flow-kit] Password reset endpoint not found.
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
+
Expected a POST route matching:
|
|
76
|
+
${url}
|
|
75
77
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
throw new Error(errorMessage);
|
|
78
|
+
Fix this by either:
|
|
79
|
+
- Adding the route on your backend, or
|
|
80
|
+
- Updating config.endpoints.forgot`
|
|
81
|
+
);
|
|
82
82
|
}
|
|
83
|
-
|
|
83
|
+
throw new Error(message);
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
// src/AuthContext.tsx
|
|
@@ -175,18 +175,29 @@ function PasswordResetScreen() {
|
|
|
175
175
|
const [email, setEmail] = useState2("");
|
|
176
176
|
const [sent, setSent] = useState2(false);
|
|
177
177
|
const [error, setError] = useState2(null);
|
|
178
|
+
const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
178
179
|
const requestReset = async (e) => {
|
|
179
180
|
e.preventDefault();
|
|
180
181
|
setError(null);
|
|
182
|
+
const trimmedEmail = email.trim();
|
|
183
|
+
if (!trimmedEmail) {
|
|
184
|
+
setError("Email is required.");
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (!isValidEmail(trimmedEmail)) {
|
|
188
|
+
setError("Enter a valid email address.");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
181
191
|
try {
|
|
182
192
|
const url = makeURL(config.baseURL, config.endpoints.forgot);
|
|
183
193
|
await httpJSON(url, {
|
|
184
194
|
method: "POST",
|
|
185
|
-
body: JSON.stringify({ email })
|
|
195
|
+
body: JSON.stringify({ email: trimmedEmail })
|
|
186
196
|
});
|
|
187
197
|
setSent(true);
|
|
188
198
|
} catch (err) {
|
|
189
|
-
|
|
199
|
+
const message = err instanceof Error ? err.message : "Failed to request reset";
|
|
200
|
+
setError(message);
|
|
190
201
|
}
|
|
191
202
|
};
|
|
192
203
|
return /* @__PURE__ */ jsxs(
|
|
@@ -226,6 +237,7 @@ function PasswordResetScreen() {
|
|
|
226
237
|
/* @__PURE__ */ jsx3(
|
|
227
238
|
"label",
|
|
228
239
|
{
|
|
240
|
+
htmlFor: "afk-reset-email",
|
|
229
241
|
style: {
|
|
230
242
|
position: "absolute",
|
|
231
243
|
top: sent ? "-18px" : "-10px",
|
|
@@ -242,6 +254,7 @@ function PasswordResetScreen() {
|
|
|
242
254
|
/* @__PURE__ */ jsx3(
|
|
243
255
|
"input",
|
|
244
256
|
{
|
|
257
|
+
id: "afk-reset-email",
|
|
245
258
|
value: email,
|
|
246
259
|
onChange: (e) => setEmail(e.target.value),
|
|
247
260
|
type: "email",
|
|
@@ -304,6 +317,8 @@ function PasswordResetScreen() {
|
|
|
304
317
|
error && /* @__PURE__ */ jsx3(
|
|
305
318
|
"p",
|
|
306
319
|
{
|
|
320
|
+
role: "alert",
|
|
321
|
+
"aria-live": "polite",
|
|
307
322
|
style: {
|
|
308
323
|
marginTop: 20,
|
|
309
324
|
color: "crimson",
|
|
@@ -354,15 +369,26 @@ function LoginScreen() {
|
|
|
354
369
|
setError(null);
|
|
355
370
|
const trimmedEmail = email.trim();
|
|
356
371
|
const trimmedPassword = password.trim();
|
|
357
|
-
if (!trimmedEmail
|
|
358
|
-
setError("Email
|
|
372
|
+
if (!trimmedEmail) {
|
|
373
|
+
setError("Email is required.");
|
|
374
|
+
setSubmitting(false);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmedEmail)) {
|
|
378
|
+
setError("Enter a valid email address.");
|
|
379
|
+
setSubmitting(false);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (!trimmedPassword) {
|
|
383
|
+
setError("Password is required.");
|
|
359
384
|
setSubmitting(false);
|
|
360
385
|
return;
|
|
361
386
|
}
|
|
362
387
|
try {
|
|
363
388
|
await login(trimmedEmail, trimmedPassword);
|
|
364
389
|
} catch (err) {
|
|
365
|
-
|
|
390
|
+
const message = err instanceof Error ? err.message : "Login failed";
|
|
391
|
+
setError(message);
|
|
366
392
|
} finally {
|
|
367
393
|
setSubmitting(false);
|
|
368
394
|
}
|
|
@@ -403,6 +429,7 @@ function LoginScreen() {
|
|
|
403
429
|
/* @__PURE__ */ jsx4(
|
|
404
430
|
"label",
|
|
405
431
|
{
|
|
432
|
+
htmlFor: "afk-login-email",
|
|
406
433
|
style: {
|
|
407
434
|
position: "absolute",
|
|
408
435
|
top: "-10px",
|
|
@@ -419,6 +446,7 @@ function LoginScreen() {
|
|
|
419
446
|
/* @__PURE__ */ jsx4(
|
|
420
447
|
"input",
|
|
421
448
|
{
|
|
449
|
+
id: "afk-login-email",
|
|
422
450
|
value: email,
|
|
423
451
|
onChange: (e) => setEmail(e.target.value),
|
|
424
452
|
type: "email",
|
|
@@ -448,6 +476,7 @@ function LoginScreen() {
|
|
|
448
476
|
/* @__PURE__ */ jsx4(
|
|
449
477
|
"label",
|
|
450
478
|
{
|
|
479
|
+
htmlFor: "afk-login-password",
|
|
451
480
|
style: {
|
|
452
481
|
position: "absolute",
|
|
453
482
|
top: "-10px",
|
|
@@ -464,6 +493,7 @@ function LoginScreen() {
|
|
|
464
493
|
/* @__PURE__ */ jsx4(
|
|
465
494
|
"input",
|
|
466
495
|
{
|
|
496
|
+
id: "afk-login-password",
|
|
467
497
|
value: password,
|
|
468
498
|
onChange: (e) => setPassword(e.target.value),
|
|
469
499
|
type: "password",
|
|
@@ -528,6 +558,8 @@ function LoginScreen() {
|
|
|
528
558
|
error && /* @__PURE__ */ jsx4(
|
|
529
559
|
"p",
|
|
530
560
|
{
|
|
561
|
+
role: "alert",
|
|
562
|
+
"aria-live": "polite",
|
|
531
563
|
style: {
|
|
532
564
|
marginTop: 18,
|
|
533
565
|
color: "crimson",
|
|
@@ -551,8 +583,21 @@ function SignupScreen() {
|
|
|
551
583
|
const [name, setName] = useState4("");
|
|
552
584
|
const [email, setEmail] = useState4("");
|
|
553
585
|
const [password, setPassword] = useState4("");
|
|
586
|
+
const [confirmPassword, setConfirmPassword] = useState4("");
|
|
554
587
|
const [submitting, setSubmitting] = useState4(false);
|
|
555
588
|
const [error, setError] = useState4(null);
|
|
589
|
+
const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
590
|
+
const getPasswordStrength = (value) => {
|
|
591
|
+
let score = 0;
|
|
592
|
+
if (value.length >= 8) score++;
|
|
593
|
+
if (/[A-Z]/.test(value)) score++;
|
|
594
|
+
if (/[0-9]/.test(value)) score++;
|
|
595
|
+
if (/[^A-Za-z0-9]/.test(value)) score++;
|
|
596
|
+
if (!value) return { label: "", color: "" };
|
|
597
|
+
if (score <= 1) return { label: "Weak", color: "crimson" };
|
|
598
|
+
if (score === 2) return { label: "Medium", color: "#f39c12" };
|
|
599
|
+
return { label: "Strong", color: "#2ecc71" };
|
|
600
|
+
};
|
|
556
601
|
const onSubmit = async (e) => {
|
|
557
602
|
e.preventDefault();
|
|
558
603
|
setSubmitting(true);
|
|
@@ -560,15 +605,47 @@ function SignupScreen() {
|
|
|
560
605
|
const trimmedEmail = email.trim();
|
|
561
606
|
const trimmedPassword = password.trim();
|
|
562
607
|
const trimmedName = name.trim();
|
|
563
|
-
|
|
564
|
-
|
|
608
|
+
const trimmedConfirm = confirmPassword.trim();
|
|
609
|
+
if (!trimmedName) {
|
|
610
|
+
setError("Name is required.");
|
|
611
|
+
setSubmitting(false);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
if (!trimmedEmail) {
|
|
615
|
+
setError("Email is required.");
|
|
616
|
+
setSubmitting(false);
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
if (!isValidEmail(trimmedEmail)) {
|
|
620
|
+
setError("Enter a valid email address.");
|
|
621
|
+
setSubmitting(false);
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
if (!trimmedPassword) {
|
|
625
|
+
setError("Password is required.");
|
|
626
|
+
setSubmitting(false);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (trimmedPassword.length < 8) {
|
|
630
|
+
setError("Password must be at least 8 characters long.");
|
|
631
|
+
setSubmitting(false);
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
if (!trimmedConfirm) {
|
|
635
|
+
setError("Please confirm your password.");
|
|
636
|
+
setSubmitting(false);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (trimmedPassword !== trimmedConfirm) {
|
|
640
|
+
setError("Passwords do not match.");
|
|
565
641
|
setSubmitting(false);
|
|
566
642
|
return;
|
|
567
643
|
}
|
|
568
644
|
try {
|
|
569
|
-
await signup({ name, email, password });
|
|
645
|
+
await signup({ name: trimmedName, email: trimmedEmail, password: trimmedPassword });
|
|
570
646
|
} catch (err) {
|
|
571
|
-
|
|
647
|
+
const message = err instanceof Error ? err.message : "Signup failed";
|
|
648
|
+
setError(message);
|
|
572
649
|
} finally {
|
|
573
650
|
setSubmitting(false);
|
|
574
651
|
}
|
|
@@ -610,6 +687,7 @@ function SignupScreen() {
|
|
|
610
687
|
/* @__PURE__ */ jsx5(
|
|
611
688
|
"label",
|
|
612
689
|
{
|
|
690
|
+
htmlFor: "afk-signup-name",
|
|
613
691
|
style: {
|
|
614
692
|
position: "absolute",
|
|
615
693
|
top: "-10px",
|
|
@@ -626,6 +704,7 @@ function SignupScreen() {
|
|
|
626
704
|
/* @__PURE__ */ jsx5(
|
|
627
705
|
"input",
|
|
628
706
|
{
|
|
707
|
+
id: "afk-signup-name",
|
|
629
708
|
value: name,
|
|
630
709
|
onChange: (e) => setName(e.target.value),
|
|
631
710
|
placeholder: "Your name",
|
|
@@ -654,6 +733,7 @@ function SignupScreen() {
|
|
|
654
733
|
/* @__PURE__ */ jsx5(
|
|
655
734
|
"label",
|
|
656
735
|
{
|
|
736
|
+
htmlFor: "afk-signup-email",
|
|
657
737
|
style: {
|
|
658
738
|
position: "absolute",
|
|
659
739
|
top: "-10px",
|
|
@@ -670,6 +750,7 @@ function SignupScreen() {
|
|
|
670
750
|
/* @__PURE__ */ jsx5(
|
|
671
751
|
"input",
|
|
672
752
|
{
|
|
753
|
+
id: "afk-signup-email",
|
|
673
754
|
value: email,
|
|
674
755
|
onChange: (e) => setEmail(e.target.value),
|
|
675
756
|
type: "email",
|
|
@@ -699,6 +780,7 @@ function SignupScreen() {
|
|
|
699
780
|
/* @__PURE__ */ jsx5(
|
|
700
781
|
"label",
|
|
701
782
|
{
|
|
783
|
+
htmlFor: "afk-signup-password",
|
|
702
784
|
style: {
|
|
703
785
|
position: "absolute",
|
|
704
786
|
top: "-10px",
|
|
@@ -715,6 +797,7 @@ function SignupScreen() {
|
|
|
715
797
|
/* @__PURE__ */ jsx5(
|
|
716
798
|
"input",
|
|
717
799
|
{
|
|
800
|
+
id: "afk-signup-password",
|
|
718
801
|
value: password,
|
|
719
802
|
onChange: (e) => setPassword(e.target.value),
|
|
720
803
|
type: "password",
|
|
@@ -740,6 +823,95 @@ function SignupScreen() {
|
|
|
740
823
|
}
|
|
741
824
|
)
|
|
742
825
|
] }),
|
|
826
|
+
/* @__PURE__ */ jsxs3("div", { style: { position: "relative", marginBottom: 18 }, children: [
|
|
827
|
+
/* @__PURE__ */ jsx5(
|
|
828
|
+
"label",
|
|
829
|
+
{
|
|
830
|
+
htmlFor: "afk-signup-confirm-password",
|
|
831
|
+
style: {
|
|
832
|
+
position: "absolute",
|
|
833
|
+
top: "-10px",
|
|
834
|
+
left: "14px",
|
|
835
|
+
background: "rgba(255,255,255,0.85)",
|
|
836
|
+
padding: "0 6px",
|
|
837
|
+
fontSize: 13,
|
|
838
|
+
color: "#444",
|
|
839
|
+
borderRadius: 6
|
|
840
|
+
},
|
|
841
|
+
children: "Confirm password"
|
|
842
|
+
}
|
|
843
|
+
),
|
|
844
|
+
/* @__PURE__ */ jsx5(
|
|
845
|
+
"input",
|
|
846
|
+
{
|
|
847
|
+
id: "afk-signup-confirm-password",
|
|
848
|
+
value: confirmPassword,
|
|
849
|
+
onChange: (e) => setConfirmPassword(e.target.value),
|
|
850
|
+
type: "password",
|
|
851
|
+
placeholder: "Repeat your password",
|
|
852
|
+
style: {
|
|
853
|
+
width: "80%",
|
|
854
|
+
padding: "14px 16px",
|
|
855
|
+
borderRadius: 12,
|
|
856
|
+
border: "1px solid #d2d2d2",
|
|
857
|
+
fontSize: 15,
|
|
858
|
+
outline: "none",
|
|
859
|
+
transition: "0.25s",
|
|
860
|
+
background: "rgba(255,255,255,0.85)"
|
|
861
|
+
},
|
|
862
|
+
onFocus: (e) => {
|
|
863
|
+
e.currentTarget.style.border = "1px solid #4b4bff";
|
|
864
|
+
e.currentTarget.style.boxShadow = "0 0 0 3px rgba(75,75,255,0.25)";
|
|
865
|
+
},
|
|
866
|
+
onBlur: (e) => {
|
|
867
|
+
e.currentTarget.style.border = "1px solid #d2d2d2";
|
|
868
|
+
e.currentTarget.style.boxShadow = "0 0 0 transparent";
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
)
|
|
872
|
+
] }),
|
|
873
|
+
/* @__PURE__ */ jsxs3(
|
|
874
|
+
"div",
|
|
875
|
+
{
|
|
876
|
+
style: {
|
|
877
|
+
marginBottom: 20,
|
|
878
|
+
fontSize: 12,
|
|
879
|
+
color: "#555"
|
|
880
|
+
},
|
|
881
|
+
children: [
|
|
882
|
+
/* @__PURE__ */ jsx5("p", { style: { marginBottom: 6 }, children: "Use at least 8 characters, including letters, numbers, and symbols." }),
|
|
883
|
+
password && /* @__PURE__ */ jsxs3(
|
|
884
|
+
"p",
|
|
885
|
+
{
|
|
886
|
+
style: {
|
|
887
|
+
fontWeight: 600,
|
|
888
|
+
color: getPasswordStrength(password).color
|
|
889
|
+
},
|
|
890
|
+
children: [
|
|
891
|
+
"Password strength: ",
|
|
892
|
+
getPasswordStrength(password).label
|
|
893
|
+
]
|
|
894
|
+
}
|
|
895
|
+
)
|
|
896
|
+
]
|
|
897
|
+
}
|
|
898
|
+
),
|
|
899
|
+
/* @__PURE__ */ jsx5(
|
|
900
|
+
"p",
|
|
901
|
+
{
|
|
902
|
+
style: {
|
|
903
|
+
textAlign: "center",
|
|
904
|
+
fontSize: 13,
|
|
905
|
+
color: "#4b4bff",
|
|
906
|
+
cursor: "pointer",
|
|
907
|
+
marginBottom: 24
|
|
908
|
+
},
|
|
909
|
+
onClick: () => {
|
|
910
|
+
window.dispatchEvent(new CustomEvent("auth-flow-kit:navigate-to-login"));
|
|
911
|
+
},
|
|
912
|
+
children: "Already have an account? Sign in"
|
|
913
|
+
}
|
|
914
|
+
),
|
|
743
915
|
/* @__PURE__ */ jsx5(
|
|
744
916
|
"button",
|
|
745
917
|
{
|
|
@@ -765,6 +937,8 @@ function SignupScreen() {
|
|
|
765
937
|
error && /* @__PURE__ */ jsx5(
|
|
766
938
|
"p",
|
|
767
939
|
{
|
|
940
|
+
role: "alert",
|
|
941
|
+
"aria-live": "polite",
|
|
768
942
|
style: {
|
|
769
943
|
marginTop: 18,
|
|
770
944
|
color: "crimson",
|