@kendevelops/auth-flow-kit 1.2.2 → 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 +229 -43
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +229 -43
- package/dist/index.js.map +1 -1
- package/package.json +45 -45
package/dist/index.cjs
CHANGED
|
@@ -34,7 +34,9 @@ var import_react = require("react");
|
|
|
34
34
|
|
|
35
35
|
// src/http.ts
|
|
36
36
|
function makeURL(baseURL, path) {
|
|
37
|
-
|
|
37
|
+
const normalizedBase = baseURL.replace(/\/$/, "");
|
|
38
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
39
|
+
return `${normalizedBase}${normalizedPath}`;
|
|
38
40
|
}
|
|
39
41
|
function getStoredAccessToken() {
|
|
40
42
|
try {
|
|
@@ -45,8 +47,11 @@ function getStoredAccessToken() {
|
|
|
45
47
|
}
|
|
46
48
|
function setStoredAccessToken(token) {
|
|
47
49
|
try {
|
|
48
|
-
if (token
|
|
49
|
-
|
|
50
|
+
if (token !== null) {
|
|
51
|
+
localStorage.setItem("afk_access_token", token);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
localStorage.removeItem("afk_access_token");
|
|
50
55
|
} catch {
|
|
51
56
|
}
|
|
52
57
|
}
|
|
@@ -54,46 +59,53 @@ async function httpJSON(url, opts = {}, withAuth = false) {
|
|
|
54
59
|
const headers = {
|
|
55
60
|
"Content-Type": "application/json"
|
|
56
61
|
};
|
|
57
|
-
if (withAuth) {
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
62
|
+
if (withAuth === true) {
|
|
63
|
+
const token = getStoredAccessToken();
|
|
64
|
+
if (token) {
|
|
65
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
66
|
+
}
|
|
60
67
|
}
|
|
61
|
-
const
|
|
68
|
+
const response = await fetch(url, {
|
|
62
69
|
...opts,
|
|
63
|
-
headers: {
|
|
70
|
+
headers: {
|
|
71
|
+
...headers,
|
|
72
|
+
...opts.headers || {}
|
|
73
|
+
}
|
|
64
74
|
});
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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;
|
|
73
85
|
}
|
|
86
|
+
} catch {
|
|
74
87
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
88
|
+
}
|
|
89
|
+
if (contentType.includes("text/html")) {
|
|
90
|
+
if (response.status === 404 && url.includes("forgot")) {
|
|
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";
|
|
81
94
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
}
|
|
96
|
+
if (response.status === 404 && url.includes("forgot")) {
|
|
97
|
+
console.error(
|
|
98
|
+
`[auth-flow-kit] Password reset endpoint not found.
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
Expected a POST route matching:
|
|
101
|
+
${url}
|
|
88
102
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
throw new Error(message);
|
|
103
|
+
Fix this by either:
|
|
104
|
+
- Adding the route on your backend, or
|
|
105
|
+
- Updating config.endpoints.forgot`
|
|
106
|
+
);
|
|
95
107
|
}
|
|
96
|
-
|
|
108
|
+
throw new Error(message);
|
|
97
109
|
}
|
|
98
110
|
|
|
99
111
|
// src/AuthContext.tsx
|
|
@@ -188,18 +200,29 @@ function PasswordResetScreen() {
|
|
|
188
200
|
const [email, setEmail] = (0, import_react3.useState)("");
|
|
189
201
|
const [sent, setSent] = (0, import_react3.useState)(false);
|
|
190
202
|
const [error, setError] = (0, import_react3.useState)(null);
|
|
203
|
+
const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
191
204
|
const requestReset = async (e) => {
|
|
192
205
|
e.preventDefault();
|
|
193
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
|
+
}
|
|
194
216
|
try {
|
|
195
217
|
const url = makeURL(config.baseURL, config.endpoints.forgot);
|
|
196
218
|
await httpJSON(url, {
|
|
197
219
|
method: "POST",
|
|
198
|
-
body: JSON.stringify({ email })
|
|
220
|
+
body: JSON.stringify({ email: trimmedEmail })
|
|
199
221
|
});
|
|
200
222
|
setSent(true);
|
|
201
223
|
} catch (err) {
|
|
202
|
-
|
|
224
|
+
const message = err instanceof Error ? err.message : "Failed to request reset";
|
|
225
|
+
setError(message);
|
|
203
226
|
}
|
|
204
227
|
};
|
|
205
228
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
@@ -239,6 +262,7 @@ function PasswordResetScreen() {
|
|
|
239
262
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
240
263
|
"label",
|
|
241
264
|
{
|
|
265
|
+
htmlFor: "afk-reset-email",
|
|
242
266
|
style: {
|
|
243
267
|
position: "absolute",
|
|
244
268
|
top: sent ? "-18px" : "-10px",
|
|
@@ -255,6 +279,7 @@ function PasswordResetScreen() {
|
|
|
255
279
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
256
280
|
"input",
|
|
257
281
|
{
|
|
282
|
+
id: "afk-reset-email",
|
|
258
283
|
value: email,
|
|
259
284
|
onChange: (e) => setEmail(e.target.value),
|
|
260
285
|
type: "email",
|
|
@@ -317,6 +342,8 @@ function PasswordResetScreen() {
|
|
|
317
342
|
error && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
318
343
|
"p",
|
|
319
344
|
{
|
|
345
|
+
role: "alert",
|
|
346
|
+
"aria-live": "polite",
|
|
320
347
|
style: {
|
|
321
348
|
marginTop: 20,
|
|
322
349
|
color: "crimson",
|
|
@@ -367,15 +394,26 @@ function LoginScreen() {
|
|
|
367
394
|
setError(null);
|
|
368
395
|
const trimmedEmail = email.trim();
|
|
369
396
|
const trimmedPassword = password.trim();
|
|
370
|
-
if (!trimmedEmail
|
|
371
|
-
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.");
|
|
372
409
|
setSubmitting(false);
|
|
373
410
|
return;
|
|
374
411
|
}
|
|
375
412
|
try {
|
|
376
413
|
await login(trimmedEmail, trimmedPassword);
|
|
377
414
|
} catch (err) {
|
|
378
|
-
|
|
415
|
+
const message = err instanceof Error ? err.message : "Login failed";
|
|
416
|
+
setError(message);
|
|
379
417
|
} finally {
|
|
380
418
|
setSubmitting(false);
|
|
381
419
|
}
|
|
@@ -416,6 +454,7 @@ function LoginScreen() {
|
|
|
416
454
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
417
455
|
"label",
|
|
418
456
|
{
|
|
457
|
+
htmlFor: "afk-login-email",
|
|
419
458
|
style: {
|
|
420
459
|
position: "absolute",
|
|
421
460
|
top: "-10px",
|
|
@@ -432,6 +471,7 @@ function LoginScreen() {
|
|
|
432
471
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
433
472
|
"input",
|
|
434
473
|
{
|
|
474
|
+
id: "afk-login-email",
|
|
435
475
|
value: email,
|
|
436
476
|
onChange: (e) => setEmail(e.target.value),
|
|
437
477
|
type: "email",
|
|
@@ -461,6 +501,7 @@ function LoginScreen() {
|
|
|
461
501
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
462
502
|
"label",
|
|
463
503
|
{
|
|
504
|
+
htmlFor: "afk-login-password",
|
|
464
505
|
style: {
|
|
465
506
|
position: "absolute",
|
|
466
507
|
top: "-10px",
|
|
@@ -477,6 +518,7 @@ function LoginScreen() {
|
|
|
477
518
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
478
519
|
"input",
|
|
479
520
|
{
|
|
521
|
+
id: "afk-login-password",
|
|
480
522
|
value: password,
|
|
481
523
|
onChange: (e) => setPassword(e.target.value),
|
|
482
524
|
type: "password",
|
|
@@ -541,6 +583,8 @@ function LoginScreen() {
|
|
|
541
583
|
error && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
542
584
|
"p",
|
|
543
585
|
{
|
|
586
|
+
role: "alert",
|
|
587
|
+
"aria-live": "polite",
|
|
544
588
|
style: {
|
|
545
589
|
marginTop: 18,
|
|
546
590
|
color: "crimson",
|
|
@@ -564,8 +608,21 @@ function SignupScreen() {
|
|
|
564
608
|
const [name, setName] = (0, import_react5.useState)("");
|
|
565
609
|
const [email, setEmail] = (0, import_react5.useState)("");
|
|
566
610
|
const [password, setPassword] = (0, import_react5.useState)("");
|
|
611
|
+
const [confirmPassword, setConfirmPassword] = (0, import_react5.useState)("");
|
|
567
612
|
const [submitting, setSubmitting] = (0, import_react5.useState)(false);
|
|
568
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
|
+
};
|
|
569
626
|
const onSubmit = async (e) => {
|
|
570
627
|
e.preventDefault();
|
|
571
628
|
setSubmitting(true);
|
|
@@ -573,15 +630,47 @@ function SignupScreen() {
|
|
|
573
630
|
const trimmedEmail = email.trim();
|
|
574
631
|
const trimmedPassword = password.trim();
|
|
575
632
|
const trimmedName = name.trim();
|
|
576
|
-
|
|
577
|
-
|
|
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.");
|
|
578
666
|
setSubmitting(false);
|
|
579
667
|
return;
|
|
580
668
|
}
|
|
581
669
|
try {
|
|
582
|
-
await signup({ name, email, password });
|
|
670
|
+
await signup({ name: trimmedName, email: trimmedEmail, password: trimmedPassword });
|
|
583
671
|
} catch (err) {
|
|
584
|
-
|
|
672
|
+
const message = err instanceof Error ? err.message : "Signup failed";
|
|
673
|
+
setError(message);
|
|
585
674
|
} finally {
|
|
586
675
|
setSubmitting(false);
|
|
587
676
|
}
|
|
@@ -623,6 +712,7 @@ function SignupScreen() {
|
|
|
623
712
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
624
713
|
"label",
|
|
625
714
|
{
|
|
715
|
+
htmlFor: "afk-signup-name",
|
|
626
716
|
style: {
|
|
627
717
|
position: "absolute",
|
|
628
718
|
top: "-10px",
|
|
@@ -639,6 +729,7 @@ function SignupScreen() {
|
|
|
639
729
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
640
730
|
"input",
|
|
641
731
|
{
|
|
732
|
+
id: "afk-signup-name",
|
|
642
733
|
value: name,
|
|
643
734
|
onChange: (e) => setName(e.target.value),
|
|
644
735
|
placeholder: "Your name",
|
|
@@ -667,6 +758,7 @@ function SignupScreen() {
|
|
|
667
758
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
668
759
|
"label",
|
|
669
760
|
{
|
|
761
|
+
htmlFor: "afk-signup-email",
|
|
670
762
|
style: {
|
|
671
763
|
position: "absolute",
|
|
672
764
|
top: "-10px",
|
|
@@ -683,6 +775,7 @@ function SignupScreen() {
|
|
|
683
775
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
684
776
|
"input",
|
|
685
777
|
{
|
|
778
|
+
id: "afk-signup-email",
|
|
686
779
|
value: email,
|
|
687
780
|
onChange: (e) => setEmail(e.target.value),
|
|
688
781
|
type: "email",
|
|
@@ -712,6 +805,7 @@ function SignupScreen() {
|
|
|
712
805
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
713
806
|
"label",
|
|
714
807
|
{
|
|
808
|
+
htmlFor: "afk-signup-password",
|
|
715
809
|
style: {
|
|
716
810
|
position: "absolute",
|
|
717
811
|
top: "-10px",
|
|
@@ -728,6 +822,7 @@ function SignupScreen() {
|
|
|
728
822
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
729
823
|
"input",
|
|
730
824
|
{
|
|
825
|
+
id: "afk-signup-password",
|
|
731
826
|
value: password,
|
|
732
827
|
onChange: (e) => setPassword(e.target.value),
|
|
733
828
|
type: "password",
|
|
@@ -753,6 +848,95 @@ function SignupScreen() {
|
|
|
753
848
|
}
|
|
754
849
|
)
|
|
755
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
|
+
),
|
|
756
940
|
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
757
941
|
"button",
|
|
758
942
|
{
|
|
@@ -778,6 +962,8 @@ function SignupScreen() {
|
|
|
778
962
|
error && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
779
963
|
"p",
|
|
780
964
|
{
|
|
965
|
+
role: "alert",
|
|
966
|
+
"aria-live": "polite",
|
|
781
967
|
style: {
|
|
782
968
|
marginTop: 18,
|
|
783
969
|
color: "crimson",
|