@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.js
CHANGED
|
@@ -9,7 +9,9 @@ import {
|
|
|
9
9
|
|
|
10
10
|
// src/http.ts
|
|
11
11
|
function makeURL(baseURL, path) {
|
|
12
|
-
|
|
12
|
+
const normalizedBase = baseURL.replace(/\/$/, "");
|
|
13
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
14
|
+
return `${normalizedBase}${normalizedPath}`;
|
|
13
15
|
}
|
|
14
16
|
function getStoredAccessToken() {
|
|
15
17
|
try {
|
|
@@ -20,8 +22,11 @@ function getStoredAccessToken() {
|
|
|
20
22
|
}
|
|
21
23
|
function setStoredAccessToken(token) {
|
|
22
24
|
try {
|
|
23
|
-
if (token
|
|
24
|
-
|
|
25
|
+
if (token !== null) {
|
|
26
|
+
localStorage.setItem("afk_access_token", token);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
localStorage.removeItem("afk_access_token");
|
|
25
30
|
} catch {
|
|
26
31
|
}
|
|
27
32
|
}
|
|
@@ -29,46 +34,53 @@ async function httpJSON(url, opts = {}, withAuth = false) {
|
|
|
29
34
|
const headers = {
|
|
30
35
|
"Content-Type": "application/json"
|
|
31
36
|
};
|
|
32
|
-
if (withAuth) {
|
|
33
|
-
const
|
|
34
|
-
if (
|
|
37
|
+
if (withAuth === true) {
|
|
38
|
+
const token = getStoredAccessToken();
|
|
39
|
+
if (token) {
|
|
40
|
+
headers["Authorization"] = `Bearer ${token}`;
|
|
41
|
+
}
|
|
35
42
|
}
|
|
36
|
-
const
|
|
43
|
+
const response = await fetch(url, {
|
|
37
44
|
...opts,
|
|
38
|
-
headers: {
|
|
45
|
+
headers: {
|
|
46
|
+
...headers,
|
|
47
|
+
...opts.headers || {}
|
|
48
|
+
}
|
|
39
49
|
});
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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;
|
|
48
60
|
}
|
|
61
|
+
} catch {
|
|
49
62
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
63
|
+
}
|
|
64
|
+
if (contentType.includes("text/html")) {
|
|
65
|
+
if (response.status === 404 && url.includes("forgot")) {
|
|
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";
|
|
56
69
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
}
|
|
71
|
+
if (response.status === 404 && url.includes("forgot")) {
|
|
72
|
+
console.error(
|
|
73
|
+
`[auth-flow-kit] Password reset endpoint not found.
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
Expected a POST route matching:
|
|
76
|
+
${url}
|
|
63
77
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
69
|
-
throw new Error(message);
|
|
78
|
+
Fix this by either:
|
|
79
|
+
- Adding the route on your backend, or
|
|
80
|
+
- Updating config.endpoints.forgot`
|
|
81
|
+
);
|
|
70
82
|
}
|
|
71
|
-
|
|
83
|
+
throw new Error(message);
|
|
72
84
|
}
|
|
73
85
|
|
|
74
86
|
// src/AuthContext.tsx
|
|
@@ -163,18 +175,29 @@ function PasswordResetScreen() {
|
|
|
163
175
|
const [email, setEmail] = useState2("");
|
|
164
176
|
const [sent, setSent] = useState2(false);
|
|
165
177
|
const [error, setError] = useState2(null);
|
|
178
|
+
const isValidEmail = (value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
166
179
|
const requestReset = async (e) => {
|
|
167
180
|
e.preventDefault();
|
|
168
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
|
+
}
|
|
169
191
|
try {
|
|
170
192
|
const url = makeURL(config.baseURL, config.endpoints.forgot);
|
|
171
193
|
await httpJSON(url, {
|
|
172
194
|
method: "POST",
|
|
173
|
-
body: JSON.stringify({ email })
|
|
195
|
+
body: JSON.stringify({ email: trimmedEmail })
|
|
174
196
|
});
|
|
175
197
|
setSent(true);
|
|
176
198
|
} catch (err) {
|
|
177
|
-
|
|
199
|
+
const message = err instanceof Error ? err.message : "Failed to request reset";
|
|
200
|
+
setError(message);
|
|
178
201
|
}
|
|
179
202
|
};
|
|
180
203
|
return /* @__PURE__ */ jsxs(
|
|
@@ -214,6 +237,7 @@ function PasswordResetScreen() {
|
|
|
214
237
|
/* @__PURE__ */ jsx3(
|
|
215
238
|
"label",
|
|
216
239
|
{
|
|
240
|
+
htmlFor: "afk-reset-email",
|
|
217
241
|
style: {
|
|
218
242
|
position: "absolute",
|
|
219
243
|
top: sent ? "-18px" : "-10px",
|
|
@@ -230,6 +254,7 @@ function PasswordResetScreen() {
|
|
|
230
254
|
/* @__PURE__ */ jsx3(
|
|
231
255
|
"input",
|
|
232
256
|
{
|
|
257
|
+
id: "afk-reset-email",
|
|
233
258
|
value: email,
|
|
234
259
|
onChange: (e) => setEmail(e.target.value),
|
|
235
260
|
type: "email",
|
|
@@ -292,6 +317,8 @@ function PasswordResetScreen() {
|
|
|
292
317
|
error && /* @__PURE__ */ jsx3(
|
|
293
318
|
"p",
|
|
294
319
|
{
|
|
320
|
+
role: "alert",
|
|
321
|
+
"aria-live": "polite",
|
|
295
322
|
style: {
|
|
296
323
|
marginTop: 20,
|
|
297
324
|
color: "crimson",
|
|
@@ -342,15 +369,26 @@ function LoginScreen() {
|
|
|
342
369
|
setError(null);
|
|
343
370
|
const trimmedEmail = email.trim();
|
|
344
371
|
const trimmedPassword = password.trim();
|
|
345
|
-
if (!trimmedEmail
|
|
346
|
-
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.");
|
|
347
384
|
setSubmitting(false);
|
|
348
385
|
return;
|
|
349
386
|
}
|
|
350
387
|
try {
|
|
351
388
|
await login(trimmedEmail, trimmedPassword);
|
|
352
389
|
} catch (err) {
|
|
353
|
-
|
|
390
|
+
const message = err instanceof Error ? err.message : "Login failed";
|
|
391
|
+
setError(message);
|
|
354
392
|
} finally {
|
|
355
393
|
setSubmitting(false);
|
|
356
394
|
}
|
|
@@ -391,6 +429,7 @@ function LoginScreen() {
|
|
|
391
429
|
/* @__PURE__ */ jsx4(
|
|
392
430
|
"label",
|
|
393
431
|
{
|
|
432
|
+
htmlFor: "afk-login-email",
|
|
394
433
|
style: {
|
|
395
434
|
position: "absolute",
|
|
396
435
|
top: "-10px",
|
|
@@ -407,6 +446,7 @@ function LoginScreen() {
|
|
|
407
446
|
/* @__PURE__ */ jsx4(
|
|
408
447
|
"input",
|
|
409
448
|
{
|
|
449
|
+
id: "afk-login-email",
|
|
410
450
|
value: email,
|
|
411
451
|
onChange: (e) => setEmail(e.target.value),
|
|
412
452
|
type: "email",
|
|
@@ -436,6 +476,7 @@ function LoginScreen() {
|
|
|
436
476
|
/* @__PURE__ */ jsx4(
|
|
437
477
|
"label",
|
|
438
478
|
{
|
|
479
|
+
htmlFor: "afk-login-password",
|
|
439
480
|
style: {
|
|
440
481
|
position: "absolute",
|
|
441
482
|
top: "-10px",
|
|
@@ -452,6 +493,7 @@ function LoginScreen() {
|
|
|
452
493
|
/* @__PURE__ */ jsx4(
|
|
453
494
|
"input",
|
|
454
495
|
{
|
|
496
|
+
id: "afk-login-password",
|
|
455
497
|
value: password,
|
|
456
498
|
onChange: (e) => setPassword(e.target.value),
|
|
457
499
|
type: "password",
|
|
@@ -516,6 +558,8 @@ function LoginScreen() {
|
|
|
516
558
|
error && /* @__PURE__ */ jsx4(
|
|
517
559
|
"p",
|
|
518
560
|
{
|
|
561
|
+
role: "alert",
|
|
562
|
+
"aria-live": "polite",
|
|
519
563
|
style: {
|
|
520
564
|
marginTop: 18,
|
|
521
565
|
color: "crimson",
|
|
@@ -539,8 +583,21 @@ function SignupScreen() {
|
|
|
539
583
|
const [name, setName] = useState4("");
|
|
540
584
|
const [email, setEmail] = useState4("");
|
|
541
585
|
const [password, setPassword] = useState4("");
|
|
586
|
+
const [confirmPassword, setConfirmPassword] = useState4("");
|
|
542
587
|
const [submitting, setSubmitting] = useState4(false);
|
|
543
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
|
+
};
|
|
544
601
|
const onSubmit = async (e) => {
|
|
545
602
|
e.preventDefault();
|
|
546
603
|
setSubmitting(true);
|
|
@@ -548,15 +605,47 @@ function SignupScreen() {
|
|
|
548
605
|
const trimmedEmail = email.trim();
|
|
549
606
|
const trimmedPassword = password.trim();
|
|
550
607
|
const trimmedName = name.trim();
|
|
551
|
-
|
|
552
|
-
|
|
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.");
|
|
553
641
|
setSubmitting(false);
|
|
554
642
|
return;
|
|
555
643
|
}
|
|
556
644
|
try {
|
|
557
|
-
await signup({ name, email, password });
|
|
645
|
+
await signup({ name: trimmedName, email: trimmedEmail, password: trimmedPassword });
|
|
558
646
|
} catch (err) {
|
|
559
|
-
|
|
647
|
+
const message = err instanceof Error ? err.message : "Signup failed";
|
|
648
|
+
setError(message);
|
|
560
649
|
} finally {
|
|
561
650
|
setSubmitting(false);
|
|
562
651
|
}
|
|
@@ -598,6 +687,7 @@ function SignupScreen() {
|
|
|
598
687
|
/* @__PURE__ */ jsx5(
|
|
599
688
|
"label",
|
|
600
689
|
{
|
|
690
|
+
htmlFor: "afk-signup-name",
|
|
601
691
|
style: {
|
|
602
692
|
position: "absolute",
|
|
603
693
|
top: "-10px",
|
|
@@ -614,6 +704,7 @@ function SignupScreen() {
|
|
|
614
704
|
/* @__PURE__ */ jsx5(
|
|
615
705
|
"input",
|
|
616
706
|
{
|
|
707
|
+
id: "afk-signup-name",
|
|
617
708
|
value: name,
|
|
618
709
|
onChange: (e) => setName(e.target.value),
|
|
619
710
|
placeholder: "Your name",
|
|
@@ -642,6 +733,7 @@ function SignupScreen() {
|
|
|
642
733
|
/* @__PURE__ */ jsx5(
|
|
643
734
|
"label",
|
|
644
735
|
{
|
|
736
|
+
htmlFor: "afk-signup-email",
|
|
645
737
|
style: {
|
|
646
738
|
position: "absolute",
|
|
647
739
|
top: "-10px",
|
|
@@ -658,6 +750,7 @@ function SignupScreen() {
|
|
|
658
750
|
/* @__PURE__ */ jsx5(
|
|
659
751
|
"input",
|
|
660
752
|
{
|
|
753
|
+
id: "afk-signup-email",
|
|
661
754
|
value: email,
|
|
662
755
|
onChange: (e) => setEmail(e.target.value),
|
|
663
756
|
type: "email",
|
|
@@ -687,6 +780,7 @@ function SignupScreen() {
|
|
|
687
780
|
/* @__PURE__ */ jsx5(
|
|
688
781
|
"label",
|
|
689
782
|
{
|
|
783
|
+
htmlFor: "afk-signup-password",
|
|
690
784
|
style: {
|
|
691
785
|
position: "absolute",
|
|
692
786
|
top: "-10px",
|
|
@@ -703,6 +797,7 @@ function SignupScreen() {
|
|
|
703
797
|
/* @__PURE__ */ jsx5(
|
|
704
798
|
"input",
|
|
705
799
|
{
|
|
800
|
+
id: "afk-signup-password",
|
|
706
801
|
value: password,
|
|
707
802
|
onChange: (e) => setPassword(e.target.value),
|
|
708
803
|
type: "password",
|
|
@@ -728,6 +823,95 @@ function SignupScreen() {
|
|
|
728
823
|
}
|
|
729
824
|
)
|
|
730
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
|
+
),
|
|
731
915
|
/* @__PURE__ */ jsx5(
|
|
732
916
|
"button",
|
|
733
917
|
{
|
|
@@ -753,6 +937,8 @@ function SignupScreen() {
|
|
|
753
937
|
error && /* @__PURE__ */ jsx5(
|
|
754
938
|
"p",
|
|
755
939
|
{
|
|
940
|
+
role: "alert",
|
|
941
|
+
"aria-live": "polite",
|
|
756
942
|
style: {
|
|
757
943
|
marginTop: 18,
|
|
758
944
|
color: "crimson",
|