@tern-secure/nextjs 4.0.0 → 4.2.0

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.
Files changed (132) hide show
  1. package/dist/cjs/app-router/client/TernSecureProvider.js +17 -2
  2. package/dist/cjs/app-router/client/TernSecureProvider.js.map +1 -1
  3. package/dist/cjs/app-router/client/actions.js +49 -49
  4. package/dist/cjs/app-router/client/actions.js.map +1 -1
  5. package/dist/cjs/app-router/route-handler/internal-route.js +17 -2
  6. package/dist/cjs/app-router/route-handler/internal-route.js.map +1 -1
  7. package/dist/cjs/app-router/server/auth.js +42 -28
  8. package/dist/cjs/app-router/server/auth.js.map +1 -1
  9. package/dist/cjs/app-router/server/edge-session.js +80 -0
  10. package/dist/cjs/app-router/server/edge-session.js.map +1 -0
  11. package/dist/cjs/app-router/server/index.js +4 -0
  12. package/dist/cjs/app-router/server/index.js.map +1 -1
  13. package/dist/cjs/app-router/server/jwt.js +141 -0
  14. package/dist/cjs/app-router/server/jwt.js.map +1 -0
  15. package/dist/cjs/app-router/server/sessionTernSecure.js +14 -9
  16. package/dist/cjs/app-router/server/sessionTernSecure.js.map +1 -1
  17. package/dist/cjs/app-router/server/ternSecureMiddleware.js +134 -13
  18. package/dist/cjs/app-router/server/ternSecureMiddleware.js.map +1 -1
  19. package/dist/cjs/boundary/TernSecureClientProvider.js +163 -40
  20. package/dist/cjs/boundary/TernSecureClientProvider.js.map +1 -1
  21. package/dist/cjs/boundary/TernSecureCtx.js.map +1 -1
  22. package/dist/cjs/boundary/hooks/useAuth.js +7 -8
  23. package/dist/cjs/boundary/hooks/useAuth.js.map +1 -1
  24. package/dist/cjs/components/sign-in.js +136 -45
  25. package/dist/cjs/components/sign-in.js.map +1 -1
  26. package/dist/cjs/components/sign-out-button.js +10 -1
  27. package/dist/cjs/components/sign-out-button.js.map +1 -1
  28. package/dist/cjs/components/sign-out.js +12 -3
  29. package/dist/cjs/components/sign-out.js.map +1 -1
  30. package/dist/cjs/components/sign-up.js +10 -5
  31. package/dist/cjs/components/sign-up.js.map +1 -1
  32. package/dist/cjs/errors.js +232 -5
  33. package/dist/cjs/errors.js.map +1 -1
  34. package/dist/cjs/index.js +0 -3
  35. package/dist/cjs/index.js.map +1 -1
  36. package/dist/cjs/types.js +14 -0
  37. package/dist/cjs/types.js.map +1 -1
  38. package/dist/cjs/utils/construct.js +50 -18
  39. package/dist/cjs/utils/construct.js.map +1 -1
  40. package/dist/cjs/utils/redirect.js +57 -0
  41. package/dist/cjs/utils/redirect.js.map +1 -0
  42. package/dist/esm/app-router/client/TernSecureProvider.js +17 -2
  43. package/dist/esm/app-router/client/TernSecureProvider.js.map +1 -1
  44. package/dist/esm/app-router/client/actions.js +59 -51
  45. package/dist/esm/app-router/client/actions.js.map +1 -1
  46. package/dist/esm/app-router/route-handler/internal-route.js +13 -1
  47. package/dist/esm/app-router/route-handler/internal-route.js.map +1 -1
  48. package/dist/esm/app-router/server/auth.js +40 -28
  49. package/dist/esm/app-router/server/auth.js.map +1 -1
  50. package/dist/esm/app-router/server/edge-session.js +56 -0
  51. package/dist/esm/app-router/server/edge-session.js.map +1 -0
  52. package/dist/esm/app-router/server/index.js +4 -2
  53. package/dist/esm/app-router/server/index.js.map +1 -1
  54. package/dist/esm/app-router/server/jwt.js +117 -0
  55. package/dist/esm/app-router/server/jwt.js.map +1 -0
  56. package/dist/esm/app-router/server/sessionTernSecure.js +14 -9
  57. package/dist/esm/app-router/server/sessionTernSecure.js.map +1 -1
  58. package/dist/esm/app-router/server/ternSecureMiddleware.js +132 -13
  59. package/dist/esm/app-router/server/ternSecureMiddleware.js.map +1 -1
  60. package/dist/esm/boundary/TernSecureClientProvider.js +164 -41
  61. package/dist/esm/boundary/TernSecureClientProvider.js.map +1 -1
  62. package/dist/esm/boundary/TernSecureCtx.js.map +1 -1
  63. package/dist/esm/boundary/hooks/useAuth.js +7 -8
  64. package/dist/esm/boundary/hooks/useAuth.js.map +1 -1
  65. package/dist/esm/components/sign-in.js +137 -46
  66. package/dist/esm/components/sign-in.js.map +1 -1
  67. package/dist/esm/components/sign-out-button.js +11 -2
  68. package/dist/esm/components/sign-out-button.js.map +1 -1
  69. package/dist/esm/components/sign-out.js +13 -4
  70. package/dist/esm/components/sign-out.js.map +1 -1
  71. package/dist/esm/components/sign-up.js +10 -5
  72. package/dist/esm/components/sign-up.js.map +1 -1
  73. package/dist/esm/errors.js +228 -4
  74. package/dist/esm/errors.js.map +1 -1
  75. package/dist/esm/index.js +0 -2
  76. package/dist/esm/index.js.map +1 -1
  77. package/dist/esm/types.js +6 -0
  78. package/dist/esm/types.js.map +1 -1
  79. package/dist/esm/utils/construct.js +46 -17
  80. package/dist/esm/utils/construct.js.map +1 -1
  81. package/dist/esm/utils/redirect.js +32 -0
  82. package/dist/esm/utils/redirect.js.map +1 -0
  83. package/dist/types/app-router/client/TernSecureProvider.d.ts +14 -3
  84. package/dist/types/app-router/client/TernSecureProvider.d.ts.map +1 -1
  85. package/dist/types/app-router/client/actions.d.ts +23 -21
  86. package/dist/types/app-router/client/actions.d.ts.map +1 -1
  87. package/dist/types/app-router/route-handler/internal-route.d.ts +3 -0
  88. package/dist/types/app-router/route-handler/internal-route.d.ts.map +1 -1
  89. package/dist/types/app-router/server/auth.d.ts +13 -1
  90. package/dist/types/app-router/server/auth.d.ts.map +1 -1
  91. package/dist/types/app-router/server/edge-session.d.ts +15 -0
  92. package/dist/types/app-router/server/edge-session.d.ts.map +1 -0
  93. package/dist/types/app-router/server/index.d.ts +3 -2
  94. package/dist/types/app-router/server/index.d.ts.map +1 -1
  95. package/dist/types/app-router/server/jwt.d.ts +20 -0
  96. package/dist/types/app-router/server/jwt.d.ts.map +1 -0
  97. package/dist/types/app-router/server/sessionTernSecure.d.ts +4 -1
  98. package/dist/types/app-router/server/sessionTernSecure.d.ts.map +1 -1
  99. package/dist/types/app-router/server/ternSecureMiddleware.d.ts +17 -4
  100. package/dist/types/app-router/server/ternSecureMiddleware.d.ts.map +1 -1
  101. package/dist/types/boundary/TernSecureClientProvider.d.ts +17 -1
  102. package/dist/types/boundary/TernSecureClientProvider.d.ts.map +1 -1
  103. package/dist/types/boundary/TernSecureCtx.d.ts +3 -1
  104. package/dist/types/boundary/TernSecureCtx.d.ts.map +1 -1
  105. package/dist/types/boundary/hooks/useAuth.d.ts +4 -1
  106. package/dist/types/boundary/hooks/useAuth.d.ts.map +1 -1
  107. package/dist/types/components/sign-in.d.ts +1 -2
  108. package/dist/types/components/sign-in.d.ts.map +1 -1
  109. package/dist/types/components/sign-out-button.d.ts +2 -1
  110. package/dist/types/components/sign-out-button.d.ts.map +1 -1
  111. package/dist/types/components/sign-out.d.ts +2 -1
  112. package/dist/types/components/sign-out.d.ts.map +1 -1
  113. package/dist/types/components/sign-up.d.ts.map +1 -1
  114. package/dist/types/components/ui/alert.d.ts +1 -1
  115. package/dist/types/components/ui/button.d.ts +1 -1
  116. package/dist/types/errors.d.ts +36 -2
  117. package/dist/types/errors.d.ts.map +1 -1
  118. package/dist/types/index.d.ts +0 -1
  119. package/dist/types/index.d.ts.map +1 -1
  120. package/dist/types/types.d.ts +35 -0
  121. package/dist/types/types.d.ts.map +1 -1
  122. package/dist/types/utils/construct.d.ts +20 -4
  123. package/dist/types/utils/construct.d.ts.map +1 -1
  124. package/dist/types/utils/redirect.d.ts +9 -0
  125. package/dist/types/utils/redirect.d.ts.map +1 -0
  126. package/package.json +7 -6
  127. package/dist/cjs/boundary/hooks/useUser.js +0 -44
  128. package/dist/cjs/boundary/hooks/useUser.js.map +0 -1
  129. package/dist/esm/boundary/hooks/useUser.js +0 -20
  130. package/dist/esm/boundary/hooks/useUser.js.map +0 -1
  131. package/dist/types/boundary/hooks/useUser.d.ts +0 -7
  132. package/dist/types/boundary/hooks/useUser.d.ts.map +0 -1
@@ -28,13 +28,19 @@ var import_client_init = require("../utils/client-init");
28
28
  var import_auth = require("firebase/auth");
29
29
  var import_TernSecureCtx = require("./TernSecureCtx");
30
30
  var import_navigation = require("next/navigation");
31
+ var import_internal_route = require("../app-router/route-handler/internal-route");
32
+ var import_construct = require("../utils/construct");
31
33
  function TernSecureClientProvider({
32
34
  children,
33
- loginPath = "/sign-in",
34
- loadingComponent
35
+ loginPath = process.env.NEXT_PUBLIC_SIGN_IN_PATH || "/sign-in",
36
+ signUpPath = process.env.NEXT_PUBLIC_SIGN_UP_PATH || "/sign-up",
37
+ loadingComponent,
38
+ requiresVerification
35
39
  }) {
36
40
  const auth = (0, import_react.useMemo)(() => import_client_init.ternSecureAuth, []);
37
41
  const router = (0, import_navigation.useRouter)();
42
+ const pathname = (0, import_navigation.usePathname)();
43
+ const [isRedirecting, setIsRedirecting] = (0, import_react.useState)(false);
38
44
  const [authState, setAuthState] = (0, import_react.useState)(() => ({
39
45
  userId: null,
40
46
  isLoaded: false,
@@ -43,9 +49,58 @@ function TernSecureClientProvider({
43
49
  isVerified: false,
44
50
  isAuthenticated: false,
45
51
  token: null,
46
- email: null
52
+ email: null,
53
+ status: "loading",
54
+ requiresVerification
47
55
  }));
56
+ const constructUrlWithRedirect = (0, import_react.useCallback)(
57
+ (loginPath2, currentPath, loginPathParam, signUpPathParam) => {
58
+ const baseUrl = window.location.origin;
59
+ const signInUrl = new URL(loginPath2, baseUrl);
60
+ if (!currentPath.includes(loginPathParam) && !currentPath.includes(signUpPathParam)) {
61
+ signInUrl.searchParams.set("redirect", currentPath);
62
+ }
63
+ return signInUrl.toString();
64
+ },
65
+ []
66
+ );
67
+ const shouldRedirect = (0, import_react.useCallback)(
68
+ (pathname2, isVerified) => {
69
+ const searchParams = new URLSearchParams(window.location.search);
70
+ if ((0, import_internal_route.isBaseAuthRoute)(pathname2) && !searchParams.has("redirect")) {
71
+ return false;
72
+ }
73
+ if ((0, import_internal_route.isInternalRoute)(pathname2)) {
74
+ return false;
75
+ }
76
+ if ((0, import_internal_route.isAuthRoute)(pathname2) && (!requiresVerification || isVerified)) {
77
+ return false;
78
+ }
79
+ return true;
80
+ },
81
+ [requiresVerification]
82
+ );
83
+ const redirectToLogin = (0, import_react.useCallback)(
84
+ (currentPath) => {
85
+ const path = currentPath || pathname || "/";
86
+ if ((0, import_internal_route.isInternalRoute)(path)) {
87
+ return;
88
+ }
89
+ if ((0, import_construct.hasRedirectLoop)(path, loginPath)) {
90
+ return;
91
+ }
92
+ setIsRedirecting(true);
93
+ const loginUrl = constructUrlWithRedirect(loginPath, path, loginPath, signUpPath);
94
+ if (process.env.NODE_ENV === "production") {
95
+ window.location.href = loginUrl;
96
+ } else {
97
+ router.push(loginUrl);
98
+ }
99
+ },
100
+ [router, loginPath, signUpPath, pathname, constructUrlWithRedirect]
101
+ );
48
102
  const handleSignOut = (0, import_react.useCallback)(async (error) => {
103
+ const currentPath = window.location.pathname;
49
104
  await auth.signOut();
50
105
  setAuthState({
51
106
  isLoaded: true,
@@ -55,56 +110,124 @@ function TernSecureClientProvider({
55
110
  token: null,
56
111
  email: null,
57
112
  isVerified: false,
58
- isAuthenticated: false
113
+ isAuthenticated: false,
114
+ status: "unauthenticated",
115
+ requiresVerification
59
116
  });
60
- router.push(loginPath);
61
- }, [auth, router, loginPath]);
117
+ redirectToLogin(currentPath);
118
+ }, [auth, redirectToLogin, requiresVerification]);
62
119
  const setEmail = (0, import_react.useCallback)((email) => {
63
120
  setAuthState((prev) => ({
64
121
  ...prev,
65
122
  email
66
123
  }));
67
124
  }, []);
125
+ const getAuthError = (0, import_react.useCallback)(() => {
126
+ if (authState.error) {
127
+ const error = authState.error;
128
+ return {
129
+ success: false,
130
+ message: error.message,
131
+ error: error.code,
132
+ user: null
133
+ };
134
+ }
135
+ if (authState.requiresVerification && authState.isValid && !authState.isVerified) {
136
+ return {
137
+ success: false,
138
+ message: "Email verification required",
139
+ error: "EMAIL_NOT_VERIFIED",
140
+ user: null
141
+ };
142
+ }
143
+ if (!authState.isAuthenticated && authState.status !== "loading") {
144
+ return {
145
+ success: false,
146
+ message: "User is not authenticated",
147
+ error: "AUTHENTICATED",
148
+ user: null
149
+ };
150
+ }
151
+ return {
152
+ success: true,
153
+ user: import_client_init.ternSecureAuth.currentUser
154
+ };
155
+ }, [
156
+ authState.error,
157
+ authState.isValid,
158
+ authState.isVerified,
159
+ authState.isAuthenticated,
160
+ authState.status,
161
+ authState.requiresVerification
162
+ ]);
68
163
  (0, import_react.useEffect)(() => {
69
- const unsubscribe = (0, import_auth.onAuthStateChanged)(auth, async (user) => {
70
- if (user) {
71
- const isValid = !!user.uid;
72
- const isVerified = user.emailVerified;
73
- setAuthState({
74
- isLoaded: true,
75
- userId: user.uid,
76
- isValid,
77
- isVerified,
78
- isAuthenticated: isValid && isVerified,
79
- token: user.getIdToken(),
80
- error: null,
81
- email: user.email
82
- });
83
- } else {
84
- setAuthState({
85
- isLoaded: true,
86
- userId: null,
87
- isValid: false,
88
- isVerified: false,
89
- isAuthenticated: false,
90
- token: null,
91
- error: new Error("User is not authenticated"),
92
- email: null
93
- });
94
- if (!window.location.pathname.includes("/sign-up")) {
95
- router.push(loginPath);
164
+ let mounted = true;
165
+ let initialLoad = true;
166
+ const unsubscribe = (0, import_auth.onAuthStateChanged)(
167
+ auth,
168
+ async (user) => {
169
+ if (!mounted) return;
170
+ try {
171
+ if (user) {
172
+ const isValid = !!user.uid;
173
+ const isVerified = user.emailVerified;
174
+ const isAuthenticated = isValid && (!requiresVerification || isVerified);
175
+ setAuthState({
176
+ isLoaded: true,
177
+ userId: user.uid,
178
+ isValid,
179
+ isVerified,
180
+ isAuthenticated: isValid && isVerified,
181
+ token: user.getIdToken(),
182
+ error: null,
183
+ email: user.email,
184
+ status: isAuthenticated ? "authenticated" : "unverified",
185
+ requiresVerification
186
+ });
187
+ if (requiresVerification && !isVerified && shouldRedirect(pathname || "", isVerified)) {
188
+ if (initialLoad || !isRedirecting) {
189
+ redirectToLogin(pathname);
190
+ }
191
+ }
192
+ } else {
193
+ setAuthState({
194
+ isLoaded: true,
195
+ userId: null,
196
+ isValid: false,
197
+ isVerified: false,
198
+ isAuthenticated: false,
199
+ token: null,
200
+ error: null,
201
+ email: null,
202
+ status: "unauthenticated",
203
+ requiresVerification
204
+ });
205
+ if (shouldRedirect(pathname || "", false) && initialLoad) {
206
+ redirectToLogin();
207
+ }
208
+ }
209
+ } catch (error) {
210
+ console.error("Auth state change error:", error);
211
+ if (mounted) {
212
+ handleSignOut(error instanceof Error ? error : new Error("Authentication error occurred"));
213
+ }
214
+ } finally {
215
+ initialLoad = false;
96
216
  }
97
217
  }
98
- }, (error) => {
99
- handleSignOut(error instanceof Error ? error : new Error("Authentication error occurred"));
100
- });
101
- return () => unsubscribe();
102
- }, [auth, handleSignOut, router, loginPath]);
218
+ );
219
+ return () => {
220
+ mounted = false;
221
+ unsubscribe();
222
+ };
223
+ }, [auth, handleSignOut, redirectToLogin, requiresVerification, pathname, isRedirecting, shouldRedirect]);
103
224
  const contextValue = (0, import_react.useMemo)(() => ({
104
225
  ...authState,
105
226
  signOut: handleSignOut,
106
- setEmail
107
- }), [authState, auth, handleSignOut, setEmail]);
227
+ setEmail,
228
+ getAuthError,
229
+ redirectToLogin
230
+ }), [authState, handleSignOut, setEmail, getAuthError, redirectToLogin]);
108
231
  if (!authState.isLoaded) {
109
232
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_TernSecureCtx.TernSecureCtx.Provider, { value: contextValue, children: loadingComponent || /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-live": "polite", "aria-busy": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sr-only", children: "Loading authentication state..." }) }) });
110
233
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/boundary/TernSecureClientProvider.tsx"],"sourcesContent":["\"use client\"\n\nimport React, { useState, useEffect, useMemo, useCallback } from 'react'\nimport { ternSecureAuth } from '../utils/client-init'\nimport { onAuthStateChanged, User } from \"firebase/auth\"\nimport { TernSecureCtx, TernSecureCtxValue } from './TernSecureCtx'\nimport { type TernSecureState } from '../types'\nimport { useRouter } from 'next/navigation'\n\ninterface TernSecureClientProviderProps {\n children: React.ReactNode;\n onUserChanged?: (user: User | null) => Promise<void>;\n loginPath?: string;\n loadingComponent?: React.ReactNode;\n}\n\nexport function TernSecureClientProvider({ \n children, \n loginPath = '/sign-in',\n loadingComponent\n}: TernSecureClientProviderProps) {\n const auth = useMemo(() => ternSecureAuth, []);\n const router = useRouter();\n const [authState, setAuthState] = useState<TernSecureState>(() => ({\n userId: null,\n isLoaded: false,\n error: null,\n isValid: false,\n isVerified: false,\n isAuthenticated: false,\n token: null,\n email: null,\n }));\n\n const handleSignOut = useCallback(async (error?: Error) => {\n await auth.signOut();\n setAuthState({\n isLoaded: true,\n userId: null,\n error: error || null,\n isValid: false,\n token: null,\n email: null,\n isVerified: false,\n isAuthenticated: false,\n });\n router.push(loginPath);\n }, [auth, router, loginPath]);\n\n const setEmail = useCallback((email: string) => {\n setAuthState((prev) => ({\n ...prev,\n email,\n }))\n }, [])\n\nuseEffect(() => {\n const unsubscribe = onAuthStateChanged(auth, async (user: User | null) => {\n if (user) {\n const isValid = !!user.uid;\n const isVerified = user.emailVerified;\n setAuthState({\n isLoaded: true,\n userId: user.uid,\n isValid,\n isVerified,\n isAuthenticated: isValid && isVerified,\n token: user.getIdToken(),\n error: null,\n email: user.email,\n })\n } else {\n setAuthState({\n isLoaded: true,\n userId: null,\n isValid: false,\n isVerified: false,\n isAuthenticated: false,\n token: null,\n error: new Error('User is not authenticated'),\n email: null,\n })\n if (!window.location.pathname.includes(\"/sign-up\")) {\n router.push(loginPath)\n }\n }\n }, (error) => {\n handleSignOut(error instanceof Error ? error : new Error('Authentication error occurred'));\n })\n \n return () => unsubscribe()\n }, [auth, handleSignOut, router, loginPath])\n\n const contextValue: TernSecureCtxValue = useMemo(() => ({\n ...authState,\n signOut: handleSignOut,\n setEmail\n }), [authState, auth, handleSignOut, setEmail]);\n\n if (!authState.isLoaded) {\n return (\n <TernSecureCtx.Provider value={contextValue}>\n {loadingComponent || (\n <div aria-live=\"polite\" aria-busy=\"true\">\n <span className=\"sr-only\">Loading authentication state...</span>\n </div>\n )}\n </TernSecureCtx.Provider>\n );\n }\n\n return (\n <TernSecureCtx.Provider value={contextValue}>\n {children}\n </TernSecureCtx.Provider>\n )\n}"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAwGY;AAtGZ,mBAAiE;AACjE,yBAA+B;AAC/B,kBAAyC;AACzC,2BAAkD;AAElD,wBAA0B;AASnB,SAAS,yBAAyB;AAAA,EACvC;AAAA,EACA,YAAY;AAAA,EACZ;AACF,GAAkC;AAChC,QAAM,WAAO,sBAAQ,MAAM,mCAAgB,CAAC,CAAC;AAC7C,QAAM,aAAS,6BAAU;AACzB,QAAM,CAAC,WAAW,YAAY,QAAI,uBAA0B,OAAO;AAAA,IACjE,QAAQ;AAAA,IACR,UAAU;AAAA,IACV,OAAO;AAAA,IACP,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,iBAAiB;AAAA,IACjB,OAAO;AAAA,IACP,OAAO;AAAA,EACT,EAAE;AAEF,QAAM,oBAAgB,0BAAY,OAAO,UAAkB;AACzD,UAAM,KAAK,QAAQ;AACnB,iBAAa;AAAA,MACX,UAAU;AAAA,MACV,QAAQ;AAAA,MACR,OAAO,SAAS;AAAA,MAChB,SAAS;AAAA,MACT,OAAO;AAAA,MACP,OAAO;AAAA,MACP,YAAY;AAAA,MACZ,iBAAiB;AAAA,IACnB,CAAC;AACD,WAAO,KAAK,SAAS;AAAA,EACvB,GAAG,CAAC,MAAM,QAAQ,SAAS,CAAC;AAE5B,QAAM,eAAW,0BAAY,CAAC,UAAkB;AAC9C,iBAAa,CAAC,UAAU;AAAA,MACtB,GAAG;AAAA,MACH;AAAA,IACF,EAAE;AAAA,EACJ,GAAG,CAAC,CAAC;AAEP,8BAAU,MAAM;AACZ,UAAM,kBAAc,gCAAmB,MAAM,OAAO,SAAsB;AACxE,UAAI,MAAM;AACR,cAAM,UAAU,CAAC,CAAC,KAAK;AACvB,cAAM,aAAa,KAAK;AACxB,qBAAa;AAAA,UACX,UAAU;AAAA,UACV,QAAQ,KAAK;AAAA,UACb;AAAA,UACA;AAAA,UACA,iBAAiB,WAAW;AAAA,UAC5B,OAAO,KAAK,WAAW;AAAA,UACvB,OAAO;AAAA,UACP,OAAO,KAAK;AAAA,QACd,CAAC;AAAA,MACH,OAAO;AACL,qBAAa;AAAA,UACX,UAAU;AAAA,UACV,QAAQ;AAAA,UACR,SAAS;AAAA,UACT,YAAY;AAAA,UACZ,iBAAiB;AAAA,UACjB,OAAO;AAAA,UACP,OAAO,IAAI,MAAM,2BAA2B;AAAA,UAC5C,OAAO;AAAA,QACT,CAAC;AACD,YAAI,CAAC,OAAO,SAAS,SAAS,SAAS,UAAU,GAAG;AAClD,iBAAO,KAAK,SAAS;AAAA,QACvB;AAAA,MACF;AAAA,IACF,GAAG,CAAC,UAAU;AACZ,oBAAc,iBAAiB,QAAQ,QAAQ,IAAI,MAAM,+BAA+B,CAAC;AAAA,IAC3F,CAAC;AAED,WAAO,MAAM,YAAY;AAAA,EAC3B,GAAG,CAAC,MAAM,eAAe,QAAQ,SAAS,CAAC;AAE3C,QAAM,mBAAmC,sBAAQ,OAAO;AAAA,IACtD,GAAG;AAAA,IACH,SAAS;AAAA,IACT;AAAA,EACF,IAAI,CAAC,WAAW,MAAM,eAAe,QAAQ,CAAC;AAE9C,MAAI,CAAC,UAAU,UAAU;AACvB,WACE,4CAAC,mCAAc,UAAd,EAAuB,OAAO,cAC5B,8BACC,4CAAC,SAAI,aAAU,UAAS,aAAU,QAChC,sDAAC,UAAK,WAAU,WAAU,6CAA+B,GAC3D,GAEJ;AAAA,EAEJ;AAEA,SACI,4CAAC,mCAAc,UAAd,EAAuB,OAAO,cAC7B,UACF;AAEN;","names":[]}
1
+ {"version":3,"sources":["../../../src/boundary/TernSecureClientProvider.tsx"],"sourcesContent":["\"use client\"\n\nimport React, { useState, useEffect, useMemo, useCallback } from 'react'\nimport { ternSecureAuth } from '../utils/client-init'\nimport { onAuthStateChanged, User } from \"firebase/auth\"\nimport { TernSecureCtx, TernSecureCtxValue } from './TernSecureCtx'\nimport type { TernSecureState, AuthError, SignInResponse } from \"../types\"\nimport type { ERRORS } from '../errors'\nimport { useRouter, usePathname } from 'next/navigation'\nimport { isBaseAuthRoute, isInternalRoute, isAuthRoute} from '../app-router/route-handler/internal-route'\nimport { hasRedirectLoop } from '../utils/construct'\n\n\n\n/**\n * @internal\n * Internal provider props - not meant for direct usage\n */\ninterface TernSecureClientProviderProps {\n children: React.ReactNode\n /** Callback when user state changes */\n onUserChanged?: (user: User | null) => Promise<void>\n /** Login page path */\n loginPath?: string\n /** Signup page path */\n signUpPath?: string\n /** Custom loading component */\n loadingComponent?: React.ReactNode\n /** Whether email verification is required */\n requiresVerification: boolean\n}\n\n/**\n * @internal\n * Internal provider component that handles authentication state\n * This is wrapped by the public TernSecureProvider\n */\n\nexport function TernSecureClientProvider({ \n children, \n loginPath = process.env.NEXT_PUBLIC_SIGN_IN_PATH || '/sign-in',\n signUpPath = process.env.NEXT_PUBLIC_SIGN_UP_PATH || '/sign-up',\n loadingComponent,\n requiresVerification,\n}: TernSecureClientProviderProps) {\n const auth = useMemo(() => ternSecureAuth, []);\n const router = useRouter();\n const pathname = usePathname() // Get current pathname\n const [isRedirecting, setIsRedirecting] = useState(false)\n\n const [authState, setAuthState] = useState<TernSecureState>(() => ({\n userId: null,\n isLoaded: false,\n error: null,\n isValid: false,\n isVerified: false,\n isAuthenticated: false,\n token: null,\n email: null,\n status: \"loading\",\n requiresVerification,\n }));\n\n const constructUrlWithRedirect = useCallback(\n (loginPath: string, currentPath: string, loginPathParam: string, signUpPathParam: string): string => {\n const baseUrl = window.location.origin\n const signInUrl = new URL(loginPath, baseUrl)\n\n // Only add redirect if not already on login or signup page\n if (!currentPath.includes(loginPathParam) && !currentPath.includes(signUpPathParam)) {\n signInUrl.searchParams.set(\"redirect\", currentPath)\n }\n return signInUrl.toString()\n },\n [],\n )\n\n\n const shouldRedirect = useCallback(\n (pathname: string, isVerified: boolean) => {\n // Get current search params\n const searchParams = new URLSearchParams(window.location.search)\n\n // Don't redirect if we're on the base sign-in page with no redirect param\n if (isBaseAuthRoute(pathname) && !searchParams.has(\"redirect\")) {\n return false\n }\n\n // Don't redirect if we're on an internal route\n if (isInternalRoute(pathname)) {\n return false\n }\n\n // Don't redirect if we're in auth routes (except when handling verification)\n if (isAuthRoute(pathname) && (!requiresVerification || isVerified)) {\n return false\n }\n\n return true\n },\n [requiresVerification],\n )\n\n const redirectToLogin = useCallback(\n (currentPath?: string) => {\n const path = currentPath || pathname || \"/\"\n\n\n if (isInternalRoute(path)) { // Don't redirect if we're already on an internal route\n return\n }\n\n // Check for redirect loops\n if (hasRedirectLoop(path, loginPath)) {\n return\n }\n\n setIsRedirecting(true)\n\n const loginUrl = constructUrlWithRedirect(loginPath, path, loginPath, signUpPath)\n\n if (process.env.NODE_ENV === \"production\") {\n window.location.href = loginUrl\n } else {\n // Use router.push for development\n router.push(loginUrl)\n }\n }, \n [router, loginPath, signUpPath, pathname, constructUrlWithRedirect]\n)\n\n const handleSignOut = useCallback(async (error?: Error) => {\n const currentPath = window.location.pathname\n await auth.signOut();\n setAuthState({\n isLoaded: true,\n userId: null,\n error: error || null,\n isValid: false,\n token: null,\n email: null,\n isVerified: false,\n isAuthenticated: false,\n status: \"unauthenticated\",\n requiresVerification,\n })\n redirectToLogin(currentPath)\n }, [auth, redirectToLogin, requiresVerification])\n\n const setEmail = useCallback((email: string) => {\n setAuthState((prev) => ({\n ...prev,\n email,\n }))\n }, [])\n\n const getAuthError = useCallback((): SignInResponse => {\n if (authState.error) {\n const error = authState.error as AuthError;\n return {\n success: false,\n message: error.message,\n error: error.code as keyof typeof ERRORS,\n user: null,\n }\n }\n\n if (authState.requiresVerification && authState.isValid && !authState.isVerified) {\n return {\n success: false,\n message: 'Email verification required',\n error: 'EMAIL_NOT_VERIFIED',\n user: null,\n }\n }\n\n if (!authState.isAuthenticated && authState.status !== \"loading\") {\n return {\n success: false,\n message: 'User is not authenticated',\n error: 'AUTHENTICATED',\n user: null,\n }\n }\n\n return {\n success: true,\n user: ternSecureAuth.currentUser,\n }\n }, [\n authState.error,\n authState.isValid,\n authState.isVerified,\n authState.isAuthenticated,\n authState.status,\n authState.requiresVerification,\n ])\n\nuseEffect(() => {\n let mounted = true\n let initialLoad = true\n\n const unsubscribe = onAuthStateChanged(\n auth,\n async (user: User | null) => {\n if (!mounted) return\n try {\n if (user) {\n const isValid = !!user.uid;\n const isVerified = user.emailVerified;\n const isAuthenticated = isValid && (!requiresVerification || isVerified) // Consider user authenticated if verification is not required or if email is verified\n\n setAuthState({\n isLoaded: true,\n userId: user.uid,\n isValid,\n isVerified,\n isAuthenticated: isValid && isVerified,\n token: user.getIdToken(),\n error: null,\n email: user.email,\n status: isAuthenticated ? \"authenticated\" : \"unverified\",\n requiresVerification,\n })\n \n if (requiresVerification && !isVerified && shouldRedirect(pathname || \"\", isVerified)) {\n if(initialLoad || !isRedirecting) {\n redirectToLogin(pathname)\n }\n }\n } else {\n setAuthState({\n isLoaded: true,\n userId: null,\n isValid: false,\n isVerified: false,\n isAuthenticated: false,\n token: null,\n error: null,\n email: null,\n status: \"unauthenticated\",\n requiresVerification,\n })\n \n if (shouldRedirect(pathname || \"\", false) && initialLoad) {\n redirectToLogin()\n }\n }\n } catch (error){\n console.error(\"Auth state change error:\", error)\n if (mounted) {\n handleSignOut(error instanceof Error ? error : new Error(\"Authentication error occurred\"))\n }\n } finally {\n initialLoad = false\n }\n })\n \n return () => {\n mounted = false\n unsubscribe()\n }\n }, [auth, handleSignOut, redirectToLogin, requiresVerification, pathname, isRedirecting, shouldRedirect])\n\n const contextValue: TernSecureCtxValue = useMemo(() => ({\n ...authState,\n signOut: handleSignOut,\n setEmail,\n getAuthError,\n redirectToLogin,\n }), [authState, handleSignOut, setEmail, getAuthError, redirectToLogin]);\n\n if (!authState.isLoaded) {\n return (\n <TernSecureCtx.Provider value={contextValue}>\n {loadingComponent || (\n <div aria-live=\"polite\" aria-busy=\"true\">\n <span className=\"sr-only\">Loading authentication state...</span>\n </div>\n )}\n </TernSecureCtx.Provider>\n );\n }\n\n return (\n <TernSecureCtx.Provider value={contextValue}>\n {children}\n </TernSecureCtx.Provider>\n )\n}"],"mappings":"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","names":["loginPath","pathname"]}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/boundary/TernSecureCtx.tsx"],"sourcesContent":["\"use client\"\n\nimport { createContext, useContext } from 'react'\nimport { ternSecureAuth } from '../utils/client-init';\nimport { User } from 'firebase/auth';\nimport { type TernSecureState } from '../types';\n\nexport const TernSecureUser = (): User | null => {\n return ternSecureAuth.currentUser;\n}\n\nexport interface TernSecureCtxValue extends TernSecureState {\n signOut: () => Promise<void>\n setEmail: (email: string) => void\n}\n\nexport const TernSecureCtx = createContext<TernSecureCtxValue | null>(null)\n\nTernSecureCtx.displayName = 'TernSecureCtx'\n\nexport const useTernSecure = (hookName: string) => {\n const context = useContext(TernSecureCtx)\n \n if (!context) {\n throw new Error(\n `${hookName} must be used within TernSecureProvider`\n )\n }\n\n return context\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBAA0C;AAC1C,yBAA+B;AAIxB,MAAM,iBAAiB,MAAmB;AAC/C,SAAO,kCAAe;AACxB;AAOO,MAAM,oBAAgB,4BAAyC,IAAI;AAE1E,cAAc,cAAc;AAErB,MAAM,gBAAgB,CAAC,aAAqB;AACjD,QAAM,cAAU,yBAAW,aAAa;AAExC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,GAAG,QAAQ;AAAA,IACb;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../src/boundary/TernSecureCtx.tsx"],"sourcesContent":["\"use client\"\n\nimport { createContext, useContext } from 'react'\nimport { ternSecureAuth } from '../utils/client-init';\nimport { User } from 'firebase/auth';\nimport type { TernSecureState, SignInResponse } from '../types';\n\nexport const TernSecureUser = (): User | null => {\n return ternSecureAuth.currentUser;\n}\n\nexport interface TernSecureCtxValue extends TernSecureState {\n signOut: () => Promise<void>\n setEmail: (email: string) => void\n getAuthError: () => SignInResponse\n redirectToLogin: () => void\n}\n\nexport const TernSecureCtx = createContext<TernSecureCtxValue | null>(null)\n\nTernSecureCtx.displayName = 'TernSecureCtx'\n\nexport const useTernSecure = (hookName: string) => {\n const context = useContext(TernSecureCtx)\n \n if (!context) {\n throw new Error(\n `${hookName} must be used within TernSecureProvider`\n )\n }\n\n return context\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,mBAA0C;AAC1C,yBAA+B;AAIxB,MAAM,iBAAiB,MAAmB;AAC/C,SAAO,kCAAe;AACxB;AASO,MAAM,oBAAgB,4BAAyC,IAAI;AAE1E,cAAc,cAAc;AAErB,MAAM,gBAAgB,CAAC,aAAqB;AACjD,QAAM,cAAU,yBAAW,aAAa;AAExC,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,GAAG,QAAQ;AAAA,IACb;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
@@ -33,21 +33,18 @@ function useAuth() {
33
33
  isVerified,
34
34
  isAuthenticated,
35
35
  token,
36
+ getAuthError,
37
+ status,
38
+ requiresVerification,
36
39
  signOut
37
40
  } = (0, import_TernSecureCtx.useTernSecure)("useAuth");
38
41
  const user = (0, import_TernSecureCtx2.TernSecureUser)();
39
- const getAuthError = () => {
40
- if (error) return error;
41
- if (isValid && !isVerified) {
42
- return new Error("Email verification required");
43
- }
44
- return null;
45
- };
42
+ const authResponse = getAuthError();
46
43
  return {
47
44
  user,
48
45
  userId,
49
46
  isLoaded,
50
- error: getAuthError(),
47
+ error: authResponse.success ? null : authResponse,
51
48
  isValid,
52
49
  // User is signed in
53
50
  isVerified,
@@ -55,6 +52,8 @@ function useAuth() {
55
52
  isAuthenticated,
56
53
  // User is both signed in and verified
57
54
  token,
55
+ status,
56
+ requiresVerification,
58
57
  signOut
59
58
  };
60
59
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/boundary/hooks/useAuth.ts"],"sourcesContent":["\"use client\"\n\nimport { useTernSecure } from '../TernSecureCtx'\nimport { User } from 'firebase/auth'\nimport { TernSecureUser } from '../TernSecureCtx'\n\nexport function useAuth() {\n const {\n userId,\n isLoaded,\n error,\n isValid,\n isVerified,\n isAuthenticated,\n token,\n signOut\n } = useTernSecure('useAuth')\n\n const user: User | null = TernSecureUser()\n\n const getAuthError = () => {\n if (error) return error\n if (isValid && !isVerified) {\n return new Error('Email verification required')\n }\n return null\n }\n\n return {\n user,\n userId,\n isLoaded,\n error: getAuthError(),\n isValid, // User is signed in\n isVerified, // Email is verified\n isAuthenticated, // User is both signed in and verified\n token,\n signOut\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,2BAA8B;AAE9B,IAAAA,wBAA+B;AAExB,SAAS,UAAU;AACxB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,QAAI,oCAAc,SAAS;AAE3B,QAAM,WAAoB,sCAAe;AAEzC,QAAM,eAAe,MAAM;AACzB,QAAI,MAAO,QAAO;AAClB,QAAI,WAAW,CAAC,YAAY;AAC1B,aAAO,IAAI,MAAM,6BAA6B;AAAA,IAChD;AACA,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,aAAa;AAAA,IACpB;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":["import_TernSecureCtx"]}
1
+ {"version":3,"sources":["../../../../src/boundary/hooks/useAuth.ts"],"sourcesContent":["\"use client\"\n\nimport { useTernSecure } from '../TernSecureCtx'\nimport { User } from 'firebase/auth'\nimport { TernSecureUser } from '../TernSecureCtx'\nimport type { SignInResponse } from '../../types'\n\n\nexport function useAuth() {\n const {\n userId,\n isLoaded,\n error,\n isValid,\n isVerified,\n isAuthenticated,\n token,\n getAuthError,\n status,\n requiresVerification,\n signOut\n } = useTernSecure('useAuth')\n\n const user: User | null = TernSecureUser()\n const authResponse: SignInResponse = getAuthError()\n\n\n return {\n user,\n userId,\n isLoaded,\n error: authResponse.success ? null : authResponse,\n isValid, // User is signed in\n isVerified, // Email is verified\n isAuthenticated, // User is both signed in and verified\n token,\n status,\n requiresVerification,\n signOut\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAEA,2BAA8B;AAE9B,IAAAA,wBAA+B;AAIxB,SAAS,UAAU;AACxB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,QAAI,oCAAc,SAAS;AAE3B,QAAM,WAAoB,sCAAe;AACzC,QAAM,eAA+B,aAAa;AAGlD,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA,OAAO,aAAa,UAAU,OAAO;AAAA,IACrC;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":["import_TernSecureCtx"]}
@@ -39,32 +39,79 @@ var import_client_init = require("../utils/client-init");
39
39
  var import_sessionTernSecure = require("../app-router/server/sessionTernSecure");
40
40
  var import_background = require("./background");
41
41
  var import_construct = require("../utils/construct");
42
- var import_errors = require("../errors");
43
42
  var import_internal_route = require("../app-router/route-handler/internal-route");
43
+ var import_useAuth = require("../boundary/hooks/useAuth");
44
+ var import_errors = require("../errors");
44
45
  const authDomain = process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN;
45
46
  const appName = process.env.NEXT_PUBLIC_FIREBASE_APP_NAME || "TernSecure";
46
47
  function SignIn({
47
48
  redirectUrl,
48
49
  onError,
49
50
  onSuccess,
50
- requiresVerification = true,
51
51
  className,
52
52
  customStyles = {}
53
53
  }) {
54
54
  const [loading, setLoading] = (0, import_react.useState)(false);
55
55
  const [checkingRedirect, setCheckingRedirect] = (0, import_react.useState)(true);
56
+ const [formError, setFormError] = (0, import_react.useState)(null);
56
57
  const [error, setError] = (0, import_react.useState)("");
57
58
  const [email, setEmail] = (0, import_react.useState)("");
58
59
  const [password, setPassword] = (0, import_react.useState)("");
60
+ const [showPassword, setShowPassword] = (0, import_react.useState)(false);
61
+ const [passwordFocused, setPasswordFocused] = (0, import_react.useState)(false);
59
62
  const [authResponse, setAuthResponse] = (0, import_react.useState)(null);
63
+ const [authErrorMessage, setAuthErrorMessage] = (0, import_react.useState)(null);
60
64
  const searchParams = (0, import_navigation.useSearchParams)();
61
65
  const isRedirectSignIn = searchParams.get("signInRedirect") === "true";
62
66
  const router = (0, import_navigation.useRouter)();
63
67
  const pathname = (0, import_navigation.usePathname)();
64
- const InternalComponent = (0, import_internal_route.handleInternalRoute)(pathname);
68
+ const InternalComponent = (0, import_internal_route.handleInternalRoute)(pathname || "");
69
+ const { requiresVerification, error: authError, status } = (0, import_useAuth.useAuth)();
70
+ const validRedirectUrl = (0, import_construct.getValidRedirectUrl)(searchParams, redirectUrl);
65
71
  if (InternalComponent) {
66
72
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(InternalComponent, {});
67
73
  }
74
+ (0, import_react.useEffect)(() => {
75
+ if (authError && status !== "loading" && status !== "unauthenticated") {
76
+ const message = authError.message || "Authentication failed";
77
+ setAuthErrorMessage(message);
78
+ if (!authResponse || authResponse.message !== message) {
79
+ setAuthResponse(authError);
80
+ }
81
+ } else {
82
+ setAuthErrorMessage(null);
83
+ }
84
+ }, [authError, status, authResponse]);
85
+ const handleSuccessfulAuth = (0, import_react.useCallback)(
86
+ async (user) => {
87
+ try {
88
+ const idToken = await user.getIdToken();
89
+ const sessionResult = await (0, import_sessionTernSecure.createSessionCookie)(idToken);
90
+ if (!sessionResult.success) {
91
+ setFormError({
92
+ success: false,
93
+ message: sessionResult.message || "Failed to create session",
94
+ error: "INTERNAL_ERROR",
95
+ user: null
96
+ });
97
+ }
98
+ onSuccess == null ? void 0 : onSuccess();
99
+ if (process.env.NODE_ENV === "production") {
100
+ window.location.href = validRedirectUrl;
101
+ } else {
102
+ router.push(validRedirectUrl);
103
+ }
104
+ } catch (err) {
105
+ setFormError({
106
+ success: false,
107
+ message: "Failed to complete authentication",
108
+ error: "INTERNAL_ERROR",
109
+ user: null
110
+ });
111
+ }
112
+ },
113
+ [validRedirectUrl, router, onSuccess]
114
+ );
68
115
  const handleRedirectResult = (0, import_react.useCallback)(async () => {
69
116
  if (!isRedirectSignIn) return false;
70
117
  setCheckingRedirect(true);
@@ -85,15 +132,16 @@ function SignIn({
85
132
  const storedRedirectUrl = sessionStorage.getItem("auth_return_url");
86
133
  sessionStorage.removeItem("auth_redirect_url");
87
134
  onSuccess == null ? void 0 : onSuccess();
88
- window.location.href = storedRedirectUrl || (0, import_construct.getValidRedirectUrl)(redirectUrl, searchParams);
135
+ window.location.href = storedRedirectUrl || (0, import_construct.getValidRedirectUrl)(searchParams, redirectUrl);
89
136
  return true;
90
137
  }
91
138
  setCheckingRedirect(false);
92
139
  } catch (err) {
93
- console.error("Redirect result error:", err);
94
- const errorMessage = err instanceof Error ? err.message : "Authentication failed";
95
- setError(errorMessage);
96
- onError == null ? void 0 : onError(err instanceof Error ? err : new Error(errorMessage));
140
+ const errorMessage = err;
141
+ setFormError(errorMessage);
142
+ if (onError && err instanceof Error) {
143
+ onError(err);
144
+ }
97
145
  sessionStorage.removeItem("auth_redirect_url");
98
146
  return false;
99
147
  }
@@ -102,32 +150,42 @@ function SignIn({
102
150
  if (isRedirectSignIn) {
103
151
  handleRedirectResult();
104
152
  }
105
- ;
106
153
  }, [handleRedirectResult, isRedirectSignIn]);
107
154
  const handleSubmit = async (e) => {
108
155
  e.preventDefault();
109
156
  setLoading(true);
157
+ setFormError(null);
110
158
  setAuthResponse(null);
111
159
  try {
112
160
  const response = await (0, import_actions.signInWithEmail)(email, password);
113
161
  setAuthResponse(response);
162
+ if (!response.success) {
163
+ setFormError({
164
+ success: false,
165
+ message: response.message,
166
+ error: response.error,
167
+ user: null
168
+ });
169
+ return;
170
+ }
114
171
  if (response.user) {
115
172
  if (requiresVerification && !response.user.emailVerified) {
116
- setError("Email verification required");
173
+ setFormError({
174
+ success: false,
175
+ message: "Email verification required",
176
+ error: "REQUIRES_VERIFICATION",
177
+ user: response.user
178
+ });
117
179
  return;
118
180
  }
119
- const idToken = await response.user.getIdToken();
120
- const sessionResult = await (0, import_sessionTernSecure.createSessionCookie)(idToken);
121
- if (!sessionResult.success) {
122
- throw new Error(sessionResult.message || "Failed to create session");
123
- }
124
- onSuccess == null ? void 0 : onSuccess();
125
- window.location.href = (0, import_construct.getValidRedirectUrl)(redirectUrl, searchParams);
181
+ await handleSuccessfulAuth(response.user);
126
182
  }
127
183
  } catch (err) {
128
- const errorMessage = err instanceof Error ? err.message : "Failed to sign in";
129
- setError(errorMessage);
130
- onError == null ? void 0 : onError(err instanceof Error ? err : new Error("Failed to sign in"));
184
+ const errorMessage = err;
185
+ setFormError(errorMessage);
186
+ if (onError && err instanceof Error) {
187
+ onError(err);
188
+ }
131
189
  } finally {
132
190
  setLoading(false);
133
191
  }
@@ -135,8 +193,8 @@ function SignIn({
135
193
  const handleSocialSignIn = async (provider) => {
136
194
  setLoading(true);
137
195
  try {
138
- const validRedirectUrl = (0, import_construct.getValidRedirectUrl)(redirectUrl, searchParams);
139
- sessionStorage.setItem("auth_redirect_url", validRedirectUrl);
196
+ const validRedirectUrl2 = (0, import_construct.getValidRedirectUrl)(searchParams, redirectUrl);
197
+ sessionStorage.setItem("auth_redirect_url", validRedirectUrl2);
140
198
  const currentUrl = new URL(window.location.href);
141
199
  currentUrl.searchParams.set("signInRedirect", "true");
142
200
  window.history.replaceState({}, "", currentUrl.toString());
@@ -145,36 +203,46 @@ function SignIn({
145
203
  throw new Error(result.error);
146
204
  }
147
205
  } catch (err) {
148
- const errorMessage = err instanceof Error ? err.message : `Failed to sign in with ${provider}`;
149
- setError(errorMessage);
150
- onError == null ? void 0 : onError(err instanceof Error ? err : new Error(`Failed to sign in with ${provider}`));
206
+ const errorMessage = err;
207
+ setFormError(errorMessage);
208
+ if (onError && err instanceof Error) {
209
+ onError(err);
210
+ }
151
211
  setLoading(false);
152
212
  sessionStorage.removeItem("auth_redirect_url");
153
213
  }
154
214
  };
215
+ const handleVerificationRedirect = (e) => {
216
+ e.preventDefault();
217
+ router.push("/sign-in/verify");
218
+ };
155
219
  if (checkingRedirect && isRedirectSignIn) {
156
220
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex min-h-screen items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "text-center space-y-4", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto" }) }) });
157
221
  }
222
+ const activeError = formError || authResponse;
223
+ const showEmailVerificationButton = (activeError == null ? void 0 : activeError.error) === "EMAIL_NOT_VERIFIED" || (activeError == null ? void 0 : activeError.error) === "REQUIRES_VERIFICATION";
158
224
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative flex items-center justify-center", children: [
159
225
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_background.AuthBackground, {}),
160
226
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_card.Card, { className: (0, import_utils.cn)("w-full max-w-md mx-auto mt-8", className, customStyles.card), children: [
161
227
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_card.CardHeader, { className: "space-y-1 text-center", children: [
162
228
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_card.CardTitle, { className: (0, import_utils.cn)("font-bold", customStyles.title), children: [
163
229
  "Sign in to ",
164
- `${appName}`
230
+ `${appName}`,
231
+ " "
165
232
  ] }),
166
233
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_card.CardDescription, { className: (0, import_utils.cn)("text-muted-foreground", customStyles.description), children: "Please sign in to continue" })
167
234
  ] }),
168
235
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_card.CardContent, { className: "space-y-4", children: [
169
236
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("form", { onSubmit: handleSubmit, className: "space-y-4", children: [
170
- error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_alert.Alert, { variant: (authResponse == null ? void 0 : authResponse.error) === import_errors.ERRORS.REQUIRES_VERIFICATION ? "destructive" : "destructive", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_alert.AlertDescription, { children: [
171
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: error }),
172
- (authResponse == null ? void 0 : authResponse.error) === import_errors.ERRORS.REQUIRES_VERIFICATION && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
237
+ activeError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_alert.Alert, { variant: (0, import_errors.getErrorAlertVariant)(activeError), className: "animate-in fade-in-50", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_alert.AlertDescription, { children: [
238
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: activeError.message }),
239
+ showEmailVerificationButton && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
173
240
  import_button.Button,
174
241
  {
242
+ type: "button",
175
243
  variant: "link",
176
244
  className: "p-0 h-auto font-normal text-sm hover:underline",
177
- onClick: () => router.push(`/sign-in/verify`),
245
+ onClick: handleVerificationRedirect,
178
246
  children: "Request new verification email \u2192"
179
247
  }
180
248
  )
@@ -191,24 +259,47 @@ function SignIn({
191
259
  onChange: (e) => setEmail(e.target.value),
192
260
  disabled: loading,
193
261
  className: (0, import_utils.cn)(customStyles.input),
194
- required: true
262
+ required: true,
263
+ "aria-invalid": (activeError == null ? void 0 : activeError.error) === "INVALID_EMAIL",
264
+ "aria-describedby": activeError ? "error-message" : void 0
195
265
  }
196
266
  )
197
267
  ] }),
198
268
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
199
269
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_label.Label, { htmlFor: "password", className: (0, import_utils.cn)(customStyles.label), children: "Password" }),
200
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
201
- import_input.Input,
202
- {
203
- id: "password",
204
- type: "password",
205
- value: password,
206
- onChange: (e) => setPassword(e.target.value),
207
- disabled: loading,
208
- className: (0, import_utils.cn)(customStyles.input),
209
- required: true
210
- }
211
- )
270
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative", children: [
271
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
272
+ import_input.Input,
273
+ {
274
+ id: "password",
275
+ name: "password",
276
+ type: showPassword ? "text" : "password",
277
+ value: password,
278
+ onChange: (e) => setPassword(e.target.value),
279
+ onFocus: () => setPasswordFocused(true),
280
+ onBlur: () => setPasswordFocused(false),
281
+ disabled: loading,
282
+ className: (0, import_utils.cn)(customStyles.input),
283
+ required: true,
284
+ "aria-invalid": (activeError == null ? void 0 : activeError.error) === "INVALID_CREDENTIALS",
285
+ "aria-describedby": activeError ? "error-message" : void 0
286
+ }
287
+ ),
288
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
289
+ import_button.Button,
290
+ {
291
+ type: "button",
292
+ variant: "ghost",
293
+ size: "icon",
294
+ className: "absolute right-2 top-1/2 -translate-y-1/2 h-8 w-8 hover:bg-transparent",
295
+ onClick: () => setShowPassword(!showPassword),
296
+ children: [
297
+ showPassword ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.EyeOff, { className: "h-4 w-4 text-muted-foreground hover:text-foreground" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Eye, { className: "h-4 w-4 text-muted-foreground hover:text-foreground" }),
298
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sr-only", children: showPassword ? "Hide password" : "Show password" })
299
+ ]
300
+ }
301
+ )
302
+ ] })
212
303
  ] }),
213
304
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_button.Button, { type: "submit", disabled: loading, className: (0, import_utils.cn)("w-full", customStyles.button), children: loading ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
214
305
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Loader2, { className: "mr-2 h-4 w-4 animate-spin" }),
@@ -216,8 +307,8 @@ function SignIn({
216
307
  ] }) : "Sign in" })
217
308
  ] }),
218
309
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative", children: [
219
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 flex items-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_separator.Separator, { className: (0, import_utils.cn)(customStyles.separator) }) }),
220
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative flex justify-center text-xs uppercase", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bg-background px-2 text-muted-foreground", children: "Or continue with" }) })
310
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_separator.Separator, { className: (0, import_utils.cn)(customStyles.separator) }),
311
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "bg-background px-2 text-muted-foreground text-sm", children: "Or continue with" }) })
221
312
  ] }),
222
313
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "grid grid-cols-2 gap-4", children: [
223
314
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(