@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.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 sanitizedBaseURL = baseURL.replace(/\/$/, "");
38
- const resolvedPath = path.startsWith("/") ? path : `/${path}`;
39
- return `${sanitizedBaseURL}${resolvedPath}`;
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 === null) {
51
- localStorage.removeItem("afk_access_token");
50
+ if (token !== null) {
51
+ localStorage.setItem("afk_access_token", token);
52
52
  return;
53
53
  }
54
- localStorage.setItem("afk_access_token", token);
54
+ localStorage.removeItem("afk_access_token");
55
55
  } catch {
56
56
  }
57
57
  }
58
58
  async function httpJSON(url, opts = {}, withAuth = false) {
59
- const requestHeaders = {
59
+ const headers = {
60
60
  "Content-Type": "application/json"
61
61
  };
62
- if (withAuth) {
63
- const accessToken = getStoredAccessToken();
64
- if (accessToken) {
65
- requestHeaders["Authorization"] = `Bearer ${accessToken}`;
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
- ...requestHeaders,
71
+ ...headers,
72
72
  ...opts.headers || {}
73
73
  }
74
74
  });
75
- if (!response.ok) {
76
- let errorMessage = `Request failed (${response.status})`;
77
- const responseType = response.headers.get("content-type") || "";
78
- if (responseType.includes("application/json")) {
79
- try {
80
- const errorPayload = await response.json();
81
- if (errorPayload?.message) {
82
- errorMessage = errorPayload.message;
83
- }
84
- } catch {
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
- console.error(
96
- `[auth-flow-kit] Password reset endpoint not found.
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
- Expected a POST route matching:
99
- ${url}
100
+ Expected a POST route matching:
101
+ ${url}
100
102
 
101
- Fix this by either:
102
- - Adding the route on your backend, or
103
- - Updating config.endpoints.forgot`
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
- return response.json();
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
- setError(err?.message || "Failed to request reset");
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 || !trimmedPassword) {
383
- setError("Email and password cannot be empty.");
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
- setError(err?.message || "Login failed");
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
- if (!trimmedEmail || !trimmedPassword || !trimmedName) {
589
- setError("Email and password cannot be empty.");
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
- setError(err?.message || "Signup failed");
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",