@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/dist/index.js CHANGED
@@ -9,7 +9,9 @@ import {
9
9
 
10
10
  // src/http.ts
11
11
  function makeURL(baseURL, path) {
12
- return `${baseURL.replace(/\/$/, "")}${path.startsWith("/") ? path : `/${path}`}`;
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) localStorage.setItem("afk_access_token", token);
24
- else localStorage.removeItem("afk_access_token");
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 tok = getStoredAccessToken();
34
- if (tok) headers["Authorization"] = `Bearer ${tok}`;
37
+ if (withAuth === true) {
38
+ const token = getStoredAccessToken();
39
+ if (token) {
40
+ headers["Authorization"] = `Bearer ${token}`;
41
+ }
35
42
  }
36
- const res = await fetch(url, {
43
+ const response = await fetch(url, {
37
44
  ...opts,
38
- headers: { ...headers, ...opts.headers || {} }
45
+ headers: {
46
+ ...headers,
47
+ ...opts.headers || {}
48
+ }
39
49
  });
40
- if (!res.ok) {
41
- let message = `Request failed (${res.status})`;
42
- const contentType = res.headers.get("content-type") || "";
43
- if (contentType.includes("application/json")) {
44
- try {
45
- const data = await res.json();
46
- if (data?.message) message = data.message;
47
- } catch {
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
- if (contentType.includes("text/html")) {
51
- if (res.status === 404 && url.includes("forgot")) {
52
- 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";
53
- } else {
54
- message = "Unexpected server error";
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
- if (res.status === 404 && url.includes("forgot")) {
58
- console.error(
59
- `[auth-flow-kit] Password reset endpoint not found.
70
+ }
71
+ if (response.status === 404 && url.includes("forgot")) {
72
+ console.error(
73
+ `[auth-flow-kit] Password reset endpoint not found.
60
74
 
61
- Expected a POST route matching:
62
- ${url}
75
+ Expected a POST route matching:
76
+ ${url}
63
77
 
64
- Fix this by either:
65
- - Adding the route on your backend, or
66
- - Updating config.endpoints.forgot`
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
- return res.json();
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
- setError(err?.message || "Failed to request reset");
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 || !trimmedPassword) {
346
- setError("Email and password cannot be empty.");
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
- setError(err?.message || "Login failed");
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
- if (!trimmedEmail || !trimmedPassword || !trimmedName) {
552
- setError("Email and password cannot be empty.");
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
- setError(err?.message || "Signup failed");
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",