@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/dist/index.js CHANGED
@@ -9,9 +9,9 @@ import {
9
9
 
10
10
  // src/http.ts
11
11
  function makeURL(baseURL, path) {
12
- const sanitizedBaseURL = baseURL.replace(/\/$/, "");
13
- const resolvedPath = path.startsWith("/") ? path : `/${path}`;
14
- return `${sanitizedBaseURL}${resolvedPath}`;
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 === null) {
26
- localStorage.removeItem("afk_access_token");
25
+ if (token !== null) {
26
+ localStorage.setItem("afk_access_token", token);
27
27
  return;
28
28
  }
29
- localStorage.setItem("afk_access_token", token);
29
+ localStorage.removeItem("afk_access_token");
30
30
  } catch {
31
31
  }
32
32
  }
33
33
  async function httpJSON(url, opts = {}, withAuth = false) {
34
- const requestHeaders = {
34
+ const headers = {
35
35
  "Content-Type": "application/json"
36
36
  };
37
- if (withAuth) {
38
- const accessToken = getStoredAccessToken();
39
- if (accessToken) {
40
- requestHeaders["Authorization"] = `Bearer ${accessToken}`;
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
- ...requestHeaders,
46
+ ...headers,
47
47
  ...opts.headers || {}
48
48
  }
49
49
  });
50
- if (!response.ok) {
51
- let errorMessage = `Request failed (${response.status})`;
52
- const responseType = response.headers.get("content-type") || "";
53
- if (responseType.includes("application/json")) {
54
- try {
55
- const errorPayload = await response.json();
56
- if (errorPayload?.message) {
57
- errorMessage = errorPayload.message;
58
- }
59
- } catch {
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
- console.error(
71
- `[auth-flow-kit] Password reset endpoint not found.
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
- Expected a POST route matching:
74
- ${url}
75
+ Expected a POST route matching:
76
+ ${url}
75
77
 
76
- Fix this by either:
77
- - Adding the route on your backend, or
78
- - Updating config.endpoints.forgot`
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
- return response.json();
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
- setError(err?.message || "Failed to request reset");
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 || !trimmedPassword) {
358
- 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.");
359
384
  setSubmitting(false);
360
385
  return;
361
386
  }
362
387
  try {
363
388
  await login(trimmedEmail, trimmedPassword);
364
389
  } catch (err) {
365
- setError(err?.message || "Login failed");
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
- if (!trimmedEmail || !trimmedPassword || !trimmedName) {
564
- 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.");
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
- setError(err?.message || "Signup failed");
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",