@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.cjs
CHANGED
|
@@ -34,9 +34,9 @@ var import_react = require("react");
|
|
|
34
34
|
|
|
35
35
|
// src/http.ts
|
|
36
36
|
function makeURL(baseURL, path) {
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
return `${
|
|
37
|
+
const normalizedBase = baseURL.replace(/\/$/, "");
|
|
38
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
39
|
+
return `${normalizedBase}${normalizedPath}`;
|
|
40
40
|
}
|
|
41
41
|
function getStoredAccessToken() {
|
|
42
42
|
try {
|
|
@@ -47,65 +47,65 @@ function getStoredAccessToken() {
|
|
|
47
47
|
}
|
|
48
48
|
function setStoredAccessToken(token) {
|
|
49
49
|
try {
|
|
50
|
-
if (token
|
|
51
|
-
localStorage.
|
|
50
|
+
if (token !== null) {
|
|
51
|
+
localStorage.setItem("afk_access_token", token);
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
|
-
localStorage.
|
|
54
|
+
localStorage.removeItem("afk_access_token");
|
|
55
55
|
} catch {
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
async function httpJSON(url, opts = {}, withAuth = false) {
|
|
59
|
-
const
|
|
59
|
+
const headers = {
|
|
60
60
|
"Content-Type": "application/json"
|
|
61
61
|
};
|
|
62
|
-
if (withAuth) {
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
|
|
62
|
+
if (withAuth === true) {
|
|
63
|
+
const token = getStoredAccessToken();
|
|
64
|
+
if (token) {
|
|
65
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
const response = await fetch(url, {
|
|
69
69
|
...opts,
|
|
70
70
|
headers: {
|
|
71
|
-
...
|
|
71
|
+
...headers,
|
|
72
72
|
...opts.headers || {}
|
|
73
73
|
}
|
|
74
74
|
});
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (responseType.includes("text/html")) {
|
|
88
|
-
if (response.status === 404 && url.includes("forgot")) {
|
|
89
|
-
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";
|
|
90
|
-
} else {
|
|
91
|
-
errorMessage = "Unexpected server error";
|
|
75
|
+
if (response.ok) {
|
|
76
|
+
return response.json();
|
|
77
|
+
}
|
|
78
|
+
let message = `Request failed (${response.status})`;
|
|
79
|
+
const contentType = response.headers.get("content-type") || "";
|
|
80
|
+
if (contentType.includes("application/json")) {
|
|
81
|
+
try {
|
|
82
|
+
const data = await response.json();
|
|
83
|
+
if (data?.message) {
|
|
84
|
+
message = data.message;
|
|
92
85
|
}
|
|
86
|
+
} catch {
|
|
93
87
|
}
|
|
88
|
+
}
|
|
89
|
+
if (contentType.includes("text/html")) {
|
|
94
90
|
if (response.status === 404 && url.includes("forgot")) {
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
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";
|
|
92
|
+
} else {
|
|
93
|
+
message = "Unexpected server error";
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (response.status === 404 && url.includes("forgot")) {
|
|
97
|
+
console.error(
|
|
98
|
+
`[auth-flow-kit] Password reset endpoint not found.
|
|
97
99
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
Expected a POST route matching:
|
|
101
|
+
${url}
|
|
100
102
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
throw new Error(errorMessage);
|
|
103
|
+
Fix this by either:
|
|
104
|
+
- Adding the route on your backend, or
|
|
105
|
+
- Updating config.endpoints.forgot`
|
|
106
|
+
);
|
|
107
107
|
}
|
|
108
|
-
|
|
108
|
+
throw new Error(message);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
// src/AuthContext.tsx
|
|
@@ -200,18 +200,29 @@ function PasswordResetScreen() {
|
|
|
200
200
|
const [email, setEmail] = (0, import_react3.useState)("");
|
|
201
201
|
const [sent, setSent] = (0, import_react3.useState)(false);
|
|
202
202
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
203
|
+
const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
203
204
|
const requestReset = async (e) => {
|
|
204
205
|
e.preventDefault();
|
|
205
206
|
setError(null);
|
|
207
|
+
const trimmedEmail = email.trim();
|
|
208
|
+
if (!trimmedEmail) {
|
|
209
|
+
setError("Email is required.");
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
if (!isValidEmail(trimmedEmail)) {
|
|
213
|
+
setError("Enter a valid email address.");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
206
216
|
try {
|
|
207
217
|
const url = makeURL(config.baseURL, config.endpoints.forgot);
|
|
208
218
|
await httpJSON(url, {
|
|
209
219
|
method: "POST",
|
|
210
|
-
body: JSON.stringify({ email })
|
|
220
|
+
body: JSON.stringify({ email: trimmedEmail })
|
|
211
221
|
});
|
|
212
222
|
setSent(true);
|
|
213
223
|
} catch (err) {
|
|
214
|
-
|
|
224
|
+
const message = err instanceof Error ? err.message : "Failed to request reset";
|
|
225
|
+
setError(message);
|
|
215
226
|
}
|
|
216
227
|
};
|
|
217
228
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
@@ -251,6 +262,7 @@ function PasswordResetScreen() {
|
|
|
251
262
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
252
263
|
"label",
|
|
253
264
|
{
|
|
265
|
+
htmlFor: "afk-reset-email",
|
|
254
266
|
style: {
|
|
255
267
|
position: "absolute",
|
|
256
268
|
top: sent ? "-18px" : "-10px",
|
|
@@ -267,6 +279,7 @@ function PasswordResetScreen() {
|
|
|
267
279
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
268
280
|
"input",
|
|
269
281
|
{
|
|
282
|
+
id: "afk-reset-email",
|
|
270
283
|
value: email,
|
|
271
284
|
onChange: (e) => setEmail(e.target.value),
|
|
272
285
|
type: "email",
|
|
@@ -329,6 +342,8 @@ function PasswordResetScreen() {
|
|
|
329
342
|
error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
330
343
|
"p",
|
|
331
344
|
{
|
|
345
|
+
role: "alert",
|
|
346
|
+
"aria-live": "polite",
|
|
332
347
|
style: {
|
|
333
348
|
marginTop: 20,
|
|
334
349
|
color: "crimson",
|
|
@@ -379,15 +394,26 @@ function LoginScreen() {
|
|
|
379
394
|
setError(null);
|
|
380
395
|
const trimmedEmail = email.trim();
|
|
381
396
|
const trimmedPassword = password.trim();
|
|
382
|
-
if (!trimmedEmail
|
|
383
|
-
setError("Email
|
|
397
|
+
if (!trimmedEmail) {
|
|
398
|
+
setError("Email is required.");
|
|
399
|
+
setSubmitting(false);
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmedEmail)) {
|
|
403
|
+
setError("Enter a valid email address.");
|
|
404
|
+
setSubmitting(false);
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
if (!trimmedPassword) {
|
|
408
|
+
setError("Password is required.");
|
|
384
409
|
setSubmitting(false);
|
|
385
410
|
return;
|
|
386
411
|
}
|
|
387
412
|
try {
|
|
388
413
|
await login(trimmedEmail, trimmedPassword);
|
|
389
414
|
} catch (err) {
|
|
390
|
-
|
|
415
|
+
const message = err instanceof Error ? err.message : "Login failed";
|
|
416
|
+
setError(message);
|
|
391
417
|
} finally {
|
|
392
418
|
setSubmitting(false);
|
|
393
419
|
}
|
|
@@ -428,6 +454,7 @@ function LoginScreen() {
|
|
|
428
454
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
429
455
|
"label",
|
|
430
456
|
{
|
|
457
|
+
htmlFor: "afk-login-email",
|
|
431
458
|
style: {
|
|
432
459
|
position: "absolute",
|
|
433
460
|
top: "-10px",
|
|
@@ -444,6 +471,7 @@ function LoginScreen() {
|
|
|
444
471
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
445
472
|
"input",
|
|
446
473
|
{
|
|
474
|
+
id: "afk-login-email",
|
|
447
475
|
value: email,
|
|
448
476
|
onChange: (e) => setEmail(e.target.value),
|
|
449
477
|
type: "email",
|
|
@@ -473,6 +501,7 @@ function LoginScreen() {
|
|
|
473
501
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
474
502
|
"label",
|
|
475
503
|
{
|
|
504
|
+
htmlFor: "afk-login-password",
|
|
476
505
|
style: {
|
|
477
506
|
position: "absolute",
|
|
478
507
|
top: "-10px",
|
|
@@ -489,6 +518,7 @@ function LoginScreen() {
|
|
|
489
518
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
490
519
|
"input",
|
|
491
520
|
{
|
|
521
|
+
id: "afk-login-password",
|
|
492
522
|
value: password,
|
|
493
523
|
onChange: (e) => setPassword(e.target.value),
|
|
494
524
|
type: "password",
|
|
@@ -553,6 +583,8 @@ function LoginScreen() {
|
|
|
553
583
|
error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
554
584
|
"p",
|
|
555
585
|
{
|
|
586
|
+
role: "alert",
|
|
587
|
+
"aria-live": "polite",
|
|
556
588
|
style: {
|
|
557
589
|
marginTop: 18,
|
|
558
590
|
color: "crimson",
|
|
@@ -576,8 +608,21 @@ function SignupScreen() {
|
|
|
576
608
|
const [name, setName] = (0, import_react5.useState)("");
|
|
577
609
|
const [email, setEmail] = (0, import_react5.useState)("");
|
|
578
610
|
const [password, setPassword] = (0, import_react5.useState)("");
|
|
611
|
+
const [confirmPassword, setConfirmPassword] = (0, import_react5.useState)("");
|
|
579
612
|
const [submitting, setSubmitting] = (0, import_react5.useState)(false);
|
|
580
613
|
const [error, setError] = (0, import_react5.useState)(null);
|
|
614
|
+
const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
615
|
+
const getPasswordStrength = (value) => {
|
|
616
|
+
let score = 0;
|
|
617
|
+
if (value.length >= 8) score++;
|
|
618
|
+
if (/[A-Z]/.test(value)) score++;
|
|
619
|
+
if (/[0-9]/.test(value)) score++;
|
|
620
|
+
if (/[^A-Za-z0-9]/.test(value)) score++;
|
|
621
|
+
if (!value) return { label: "", color: "" };
|
|
622
|
+
if (score <= 1) return { label: "Weak", color: "crimson" };
|
|
623
|
+
if (score === 2) return { label: "Medium", color: "#f39c12" };
|
|
624
|
+
return { label: "Strong", color: "#2ecc71" };
|
|
625
|
+
};
|
|
581
626
|
const onSubmit = async (e) => {
|
|
582
627
|
e.preventDefault();
|
|
583
628
|
setSubmitting(true);
|
|
@@ -585,15 +630,47 @@ function SignupScreen() {
|
|
|
585
630
|
const trimmedEmail = email.trim();
|
|
586
631
|
const trimmedPassword = password.trim();
|
|
587
632
|
const trimmedName = name.trim();
|
|
588
|
-
|
|
589
|
-
|
|
633
|
+
const trimmedConfirm = confirmPassword.trim();
|
|
634
|
+
if (!trimmedName) {
|
|
635
|
+
setError("Name is required.");
|
|
636
|
+
setSubmitting(false);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
if (!trimmedEmail) {
|
|
640
|
+
setError("Email is required.");
|
|
641
|
+
setSubmitting(false);
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
if (!isValidEmail(trimmedEmail)) {
|
|
645
|
+
setError("Enter a valid email address.");
|
|
646
|
+
setSubmitting(false);
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
if (!trimmedPassword) {
|
|
650
|
+
setError("Password is required.");
|
|
651
|
+
setSubmitting(false);
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
if (trimmedPassword.length < 8) {
|
|
655
|
+
setError("Password must be at least 8 characters long.");
|
|
656
|
+
setSubmitting(false);
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
if (!trimmedConfirm) {
|
|
660
|
+
setError("Please confirm your password.");
|
|
661
|
+
setSubmitting(false);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (trimmedPassword !== trimmedConfirm) {
|
|
665
|
+
setError("Passwords do not match.");
|
|
590
666
|
setSubmitting(false);
|
|
591
667
|
return;
|
|
592
668
|
}
|
|
593
669
|
try {
|
|
594
|
-
await signup({ name, email, password });
|
|
670
|
+
await signup({ name: trimmedName, email: trimmedEmail, password: trimmedPassword });
|
|
595
671
|
} catch (err) {
|
|
596
|
-
|
|
672
|
+
const message = err instanceof Error ? err.message : "Signup failed";
|
|
673
|
+
setError(message);
|
|
597
674
|
} finally {
|
|
598
675
|
setSubmitting(false);
|
|
599
676
|
}
|
|
@@ -635,6 +712,7 @@ function SignupScreen() {
|
|
|
635
712
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
636
713
|
"label",
|
|
637
714
|
{
|
|
715
|
+
htmlFor: "afk-signup-name",
|
|
638
716
|
style: {
|
|
639
717
|
position: "absolute",
|
|
640
718
|
top: "-10px",
|
|
@@ -651,6 +729,7 @@ function SignupScreen() {
|
|
|
651
729
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
652
730
|
"input",
|
|
653
731
|
{
|
|
732
|
+
id: "afk-signup-name",
|
|
654
733
|
value: name,
|
|
655
734
|
onChange: (e) => setName(e.target.value),
|
|
656
735
|
placeholder: "Your name",
|
|
@@ -679,6 +758,7 @@ function SignupScreen() {
|
|
|
679
758
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
680
759
|
"label",
|
|
681
760
|
{
|
|
761
|
+
htmlFor: "afk-signup-email",
|
|
682
762
|
style: {
|
|
683
763
|
position: "absolute",
|
|
684
764
|
top: "-10px",
|
|
@@ -695,6 +775,7 @@ function SignupScreen() {
|
|
|
695
775
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
696
776
|
"input",
|
|
697
777
|
{
|
|
778
|
+
id: "afk-signup-email",
|
|
698
779
|
value: email,
|
|
699
780
|
onChange: (e) => setEmail(e.target.value),
|
|
700
781
|
type: "email",
|
|
@@ -724,6 +805,7 @@ function SignupScreen() {
|
|
|
724
805
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
725
806
|
"label",
|
|
726
807
|
{
|
|
808
|
+
htmlFor: "afk-signup-password",
|
|
727
809
|
style: {
|
|
728
810
|
position: "absolute",
|
|
729
811
|
top: "-10px",
|
|
@@ -740,6 +822,7 @@ function SignupScreen() {
|
|
|
740
822
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
741
823
|
"input",
|
|
742
824
|
{
|
|
825
|
+
id: "afk-signup-password",
|
|
743
826
|
value: password,
|
|
744
827
|
onChange: (e) => setPassword(e.target.value),
|
|
745
828
|
type: "password",
|
|
@@ -765,6 +848,95 @@ function SignupScreen() {
|
|
|
765
848
|
}
|
|
766
849
|
)
|
|
767
850
|
] }),
|
|
851
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: { position: "relative", marginBottom: 18 }, children: [
|
|
852
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
853
|
+
"label",
|
|
854
|
+
{
|
|
855
|
+
htmlFor: "afk-signup-confirm-password",
|
|
856
|
+
style: {
|
|
857
|
+
position: "absolute",
|
|
858
|
+
top: "-10px",
|
|
859
|
+
left: "14px",
|
|
860
|
+
background: "rgba(255,255,255,0.85)",
|
|
861
|
+
padding: "0 6px",
|
|
862
|
+
fontSize: 13,
|
|
863
|
+
color: "#444",
|
|
864
|
+
borderRadius: 6
|
|
865
|
+
},
|
|
866
|
+
children: "Confirm password"
|
|
867
|
+
}
|
|
868
|
+
),
|
|
869
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
870
|
+
"input",
|
|
871
|
+
{
|
|
872
|
+
id: "afk-signup-confirm-password",
|
|
873
|
+
value: confirmPassword,
|
|
874
|
+
onChange: (e) => setConfirmPassword(e.target.value),
|
|
875
|
+
type: "password",
|
|
876
|
+
placeholder: "Repeat your password",
|
|
877
|
+
style: {
|
|
878
|
+
width: "80%",
|
|
879
|
+
padding: "14px 16px",
|
|
880
|
+
borderRadius: 12,
|
|
881
|
+
border: "1px solid #d2d2d2",
|
|
882
|
+
fontSize: 15,
|
|
883
|
+
outline: "none",
|
|
884
|
+
transition: "0.25s",
|
|
885
|
+
background: "rgba(255,255,255,0.85)"
|
|
886
|
+
},
|
|
887
|
+
onFocus: (e) => {
|
|
888
|
+
e.currentTarget.style.border = "1px solid #4b4bff";
|
|
889
|
+
e.currentTarget.style.boxShadow = "0 0 0 3px rgba(75,75,255,0.25)";
|
|
890
|
+
},
|
|
891
|
+
onBlur: (e) => {
|
|
892
|
+
e.currentTarget.style.border = "1px solid #d2d2d2";
|
|
893
|
+
e.currentTarget.style.boxShadow = "0 0 0 transparent";
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
)
|
|
897
|
+
] }),
|
|
898
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
899
|
+
"div",
|
|
900
|
+
{
|
|
901
|
+
style: {
|
|
902
|
+
marginBottom: 20,
|
|
903
|
+
fontSize: 12,
|
|
904
|
+
color: "#555"
|
|
905
|
+
},
|
|
906
|
+
children: [
|
|
907
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { style: { marginBottom: 6 }, children: "Use at least 8 characters, including letters, numbers, and symbols." }),
|
|
908
|
+
password && /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
909
|
+
"p",
|
|
910
|
+
{
|
|
911
|
+
style: {
|
|
912
|
+
fontWeight: 600,
|
|
913
|
+
color: getPasswordStrength(password).color
|
|
914
|
+
},
|
|
915
|
+
children: [
|
|
916
|
+
"Password strength: ",
|
|
917
|
+
getPasswordStrength(password).label
|
|
918
|
+
]
|
|
919
|
+
}
|
|
920
|
+
)
|
|
921
|
+
]
|
|
922
|
+
}
|
|
923
|
+
),
|
|
924
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
925
|
+
"p",
|
|
926
|
+
{
|
|
927
|
+
style: {
|
|
928
|
+
textAlign: "center",
|
|
929
|
+
fontSize: 13,
|
|
930
|
+
color: "#4b4bff",
|
|
931
|
+
cursor: "pointer",
|
|
932
|
+
marginBottom: 24
|
|
933
|
+
},
|
|
934
|
+
onClick: () => {
|
|
935
|
+
window.dispatchEvent(new CustomEvent("auth-flow-kit:navigate-to-login"));
|
|
936
|
+
},
|
|
937
|
+
children: "Already have an account? Sign in"
|
|
938
|
+
}
|
|
939
|
+
),
|
|
768
940
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
769
941
|
"button",
|
|
770
942
|
{
|
|
@@ -790,6 +962,8 @@ function SignupScreen() {
|
|
|
790
962
|
error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
791
963
|
"p",
|
|
792
964
|
{
|
|
965
|
+
role: "alert",
|
|
966
|
+
"aria-live": "polite",
|
|
793
967
|
style: {
|
|
794
968
|
marginTop: 18,
|
|
795
969
|
color: "crimson",
|