@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.
- package/dist/cjs/app-router/client/TernSecureProvider.js +17 -2
- package/dist/cjs/app-router/client/TernSecureProvider.js.map +1 -1
- package/dist/cjs/app-router/client/actions.js +49 -49
- package/dist/cjs/app-router/client/actions.js.map +1 -1
- package/dist/cjs/app-router/route-handler/internal-route.js +17 -2
- package/dist/cjs/app-router/route-handler/internal-route.js.map +1 -1
- package/dist/cjs/app-router/server/auth.js +42 -28
- package/dist/cjs/app-router/server/auth.js.map +1 -1
- package/dist/cjs/app-router/server/edge-session.js +80 -0
- package/dist/cjs/app-router/server/edge-session.js.map +1 -0
- package/dist/cjs/app-router/server/index.js +4 -0
- package/dist/cjs/app-router/server/index.js.map +1 -1
- package/dist/cjs/app-router/server/jwt.js +141 -0
- package/dist/cjs/app-router/server/jwt.js.map +1 -0
- package/dist/cjs/app-router/server/sessionTernSecure.js +14 -9
- package/dist/cjs/app-router/server/sessionTernSecure.js.map +1 -1
- package/dist/cjs/app-router/server/ternSecureMiddleware.js +134 -13
- package/dist/cjs/app-router/server/ternSecureMiddleware.js.map +1 -1
- package/dist/cjs/boundary/TernSecureClientProvider.js +163 -40
- package/dist/cjs/boundary/TernSecureClientProvider.js.map +1 -1
- package/dist/cjs/boundary/TernSecureCtx.js.map +1 -1
- package/dist/cjs/boundary/hooks/useAuth.js +7 -8
- package/dist/cjs/boundary/hooks/useAuth.js.map +1 -1
- package/dist/cjs/components/sign-in.js +136 -45
- package/dist/cjs/components/sign-in.js.map +1 -1
- package/dist/cjs/components/sign-out-button.js +10 -1
- package/dist/cjs/components/sign-out-button.js.map +1 -1
- package/dist/cjs/components/sign-out.js +12 -3
- package/dist/cjs/components/sign-out.js.map +1 -1
- package/dist/cjs/components/sign-up.js +10 -5
- package/dist/cjs/components/sign-up.js.map +1 -1
- package/dist/cjs/errors.js +232 -5
- package/dist/cjs/errors.js.map +1 -1
- package/dist/cjs/index.js +0 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types.js +14 -0
- package/dist/cjs/types.js.map +1 -1
- package/dist/cjs/utils/construct.js +50 -18
- package/dist/cjs/utils/construct.js.map +1 -1
- package/dist/cjs/utils/redirect.js +57 -0
- package/dist/cjs/utils/redirect.js.map +1 -0
- package/dist/esm/app-router/client/TernSecureProvider.js +17 -2
- package/dist/esm/app-router/client/TernSecureProvider.js.map +1 -1
- package/dist/esm/app-router/client/actions.js +59 -51
- package/dist/esm/app-router/client/actions.js.map +1 -1
- package/dist/esm/app-router/route-handler/internal-route.js +13 -1
- package/dist/esm/app-router/route-handler/internal-route.js.map +1 -1
- package/dist/esm/app-router/server/auth.js +40 -28
- package/dist/esm/app-router/server/auth.js.map +1 -1
- package/dist/esm/app-router/server/edge-session.js +56 -0
- package/dist/esm/app-router/server/edge-session.js.map +1 -0
- package/dist/esm/app-router/server/index.js +4 -2
- package/dist/esm/app-router/server/index.js.map +1 -1
- package/dist/esm/app-router/server/jwt.js +117 -0
- package/dist/esm/app-router/server/jwt.js.map +1 -0
- package/dist/esm/app-router/server/sessionTernSecure.js +14 -9
- package/dist/esm/app-router/server/sessionTernSecure.js.map +1 -1
- package/dist/esm/app-router/server/ternSecureMiddleware.js +132 -13
- package/dist/esm/app-router/server/ternSecureMiddleware.js.map +1 -1
- package/dist/esm/boundary/TernSecureClientProvider.js +164 -41
- package/dist/esm/boundary/TernSecureClientProvider.js.map +1 -1
- package/dist/esm/boundary/TernSecureCtx.js.map +1 -1
- package/dist/esm/boundary/hooks/useAuth.js +7 -8
- package/dist/esm/boundary/hooks/useAuth.js.map +1 -1
- package/dist/esm/components/sign-in.js +137 -46
- package/dist/esm/components/sign-in.js.map +1 -1
- package/dist/esm/components/sign-out-button.js +11 -2
- package/dist/esm/components/sign-out-button.js.map +1 -1
- package/dist/esm/components/sign-out.js +13 -4
- package/dist/esm/components/sign-out.js.map +1 -1
- package/dist/esm/components/sign-up.js +10 -5
- package/dist/esm/components/sign-up.js.map +1 -1
- package/dist/esm/errors.js +228 -4
- package/dist/esm/errors.js.map +1 -1
- package/dist/esm/index.js +0 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types.js +6 -0
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/utils/construct.js +46 -17
- package/dist/esm/utils/construct.js.map +1 -1
- package/dist/esm/utils/redirect.js +32 -0
- package/dist/esm/utils/redirect.js.map +1 -0
- package/dist/types/app-router/client/TernSecureProvider.d.ts +14 -3
- package/dist/types/app-router/client/TernSecureProvider.d.ts.map +1 -1
- package/dist/types/app-router/client/actions.d.ts +23 -21
- package/dist/types/app-router/client/actions.d.ts.map +1 -1
- package/dist/types/app-router/route-handler/internal-route.d.ts +3 -0
- package/dist/types/app-router/route-handler/internal-route.d.ts.map +1 -1
- package/dist/types/app-router/server/auth.d.ts +13 -1
- package/dist/types/app-router/server/auth.d.ts.map +1 -1
- package/dist/types/app-router/server/edge-session.d.ts +15 -0
- package/dist/types/app-router/server/edge-session.d.ts.map +1 -0
- package/dist/types/app-router/server/index.d.ts +3 -2
- package/dist/types/app-router/server/index.d.ts.map +1 -1
- package/dist/types/app-router/server/jwt.d.ts +20 -0
- package/dist/types/app-router/server/jwt.d.ts.map +1 -0
- package/dist/types/app-router/server/sessionTernSecure.d.ts +4 -1
- package/dist/types/app-router/server/sessionTernSecure.d.ts.map +1 -1
- package/dist/types/app-router/server/ternSecureMiddleware.d.ts +17 -4
- package/dist/types/app-router/server/ternSecureMiddleware.d.ts.map +1 -1
- package/dist/types/boundary/TernSecureClientProvider.d.ts +17 -1
- package/dist/types/boundary/TernSecureClientProvider.d.ts.map +1 -1
- package/dist/types/boundary/TernSecureCtx.d.ts +3 -1
- package/dist/types/boundary/TernSecureCtx.d.ts.map +1 -1
- package/dist/types/boundary/hooks/useAuth.d.ts +4 -1
- package/dist/types/boundary/hooks/useAuth.d.ts.map +1 -1
- package/dist/types/components/sign-in.d.ts +1 -2
- package/dist/types/components/sign-in.d.ts.map +1 -1
- package/dist/types/components/sign-out-button.d.ts +2 -1
- package/dist/types/components/sign-out-button.d.ts.map +1 -1
- package/dist/types/components/sign-out.d.ts +2 -1
- package/dist/types/components/sign-out.d.ts.map +1 -1
- package/dist/types/components/sign-up.d.ts.map +1 -1
- package/dist/types/components/ui/alert.d.ts +1 -1
- package/dist/types/components/ui/button.d.ts +1 -1
- package/dist/types/errors.d.ts +36 -2
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/types.d.ts +35 -0
- package/dist/types/types.d.ts.map +1 -1
- package/dist/types/utils/construct.d.ts +20 -4
- package/dist/types/utils/construct.d.ts.map +1 -1
- package/dist/types/utils/redirect.d.ts +9 -0
- package/dist/types/utils/redirect.d.ts.map +1 -0
- package/package.json +7 -6
- package/dist/cjs/boundary/hooks/useUser.js +0 -44
- package/dist/cjs/boundary/hooks/useUser.js.map +0 -1
- package/dist/esm/boundary/hooks/useUser.js +0 -20
- package/dist/esm/boundary/hooks/useUser.js.map +0 -1
- package/dist/types/boundary/hooks/useUser.d.ts +0 -7
- 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
|
-
|
|
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
|
-
|
|
61
|
-
}, [auth,
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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:
|
|
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
|
|
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)(
|
|
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
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
129
|
-
|
|
130
|
-
onError
|
|
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
|
|
139
|
-
sessionStorage.setItem("auth_redirect_url",
|
|
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
|
|
149
|
-
|
|
150
|
-
onError
|
|
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
|
-
|
|
171
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children:
|
|
172
|
-
|
|
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:
|
|
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.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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)(
|
|
220
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "
|
|
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)(
|