cloudflare-next-intl 0.2.2 → 0.3.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/README.md +2 -3
- package/dist/src/client/components/client_provider.d.ts +3 -1
- package/dist/src/client/components/client_provider.js +16 -3
- package/dist/src/client/functions/get_cookie.bench.d.ts +1 -0
- package/dist/src/client/functions/get_cookie.bench.js +11 -0
- package/dist/src/client/functions/set_cookie.bench.d.ts +1 -0
- package/dist/src/client/functions/set_cookie.bench.js +10 -0
- package/dist/src/client/functions/set_cookie.d.ts +4 -2
- package/dist/src/client/functions/set_cookie.js +3 -1
- package/dist/src/client/hooks/client_hooks.d.ts +5 -0
- package/dist/src/client/hooks/client_hooks.js +7 -2
- package/dist/src/client/index.js +3 -0
- package/dist/src/config/cookie_key.bench.d.ts +1 -0
- package/dist/src/config/cookie_key.bench.js +9 -0
- package/dist/src/config/init_config.bench.d.ts +1 -0
- package/dist/src/config/init_config.bench.js +7 -0
- package/dist/src/config/intl_config.js +4 -1
- package/dist/src/config/intl_sitemap.bench.d.ts +1 -0
- package/dist/src/config/intl_sitemap.bench.js +18 -0
- package/dist/src/config/intl_sitemap.js +1 -1
- package/dist/src/config/middleware.bench.d.ts +1 -0
- package/dist/src/config/middleware.bench.js +34 -0
- package/dist/src/config/middleware.js +39 -8
- package/dist/src/firebase_auth/client/auth_actions.bench.d.ts +1 -0
- package/dist/src/firebase_auth/client/auth_actions.bench.js +36 -0
- package/dist/src/firebase_auth/client/auth_actions.d.ts +46 -0
- package/dist/src/firebase_auth/client/auth_actions.js +102 -0
- package/dist/src/firebase_auth/client/auth_user_cache.bench.d.ts +1 -0
- package/dist/src/firebase_auth/client/auth_user_cache.bench.js +11 -0
- package/dist/src/firebase_auth/client/auth_user_cache.d.ts +4 -0
- package/dist/src/firebase_auth/client/auth_user_cache.js +14 -0
- package/dist/src/firebase_auth/client/auth_user_provider.d.ts +36 -0
- package/dist/src/firebase_auth/client/auth_user_provider.js +148 -0
- package/dist/src/firebase_auth/client/firebase_client.bench.d.ts +1 -0
- package/dist/src/firebase_auth/client/firebase_client.bench.js +28 -0
- package/dist/src/firebase_auth/client/firebase_client.d.ts +18 -0
- package/dist/src/firebase_auth/client/firebase_client.js +40 -0
- package/dist/src/firebase_auth/client/use_auth_user.d.ts +12 -0
- package/dist/src/firebase_auth/client/use_auth_user.js +20 -0
- package/dist/src/firebase_auth/error_messages/default_messages.en.bench.d.ts +1 -0
- package/dist/src/firebase_auth/error_messages/default_messages.en.bench.js +10 -0
- package/dist/src/firebase_auth/error_messages/default_messages.en.d.ts +1 -0
- package/dist/src/firebase_auth/error_messages/default_messages.en.js +14 -0
- package/dist/src/firebase_auth/error_messages/firebase_auth_error_helper.bench.d.ts +1 -0
- package/dist/src/firebase_auth/error_messages/firebase_auth_error_helper.bench.js +15 -0
- package/dist/src/firebase_auth/error_messages/firebase_auth_error_helper.d.ts +6 -0
- package/dist/src/firebase_auth/error_messages/firebase_auth_error_helper.js +42 -0
- package/dist/src/firebase_auth/index.d.ts +9 -0
- package/dist/src/firebase_auth/index.js +13 -0
- package/dist/src/firebase_auth/middleware/update_session.bench.d.ts +1 -0
- package/dist/src/firebase_auth/middleware/update_session.bench.js +37 -0
- package/dist/src/firebase_auth/middleware/update_session.d.ts +22 -0
- package/dist/src/firebase_auth/middleware/update_session.js +213 -0
- package/dist/src/firebase_auth/require_config.bench.d.ts +1 -0
- package/dist/src/firebase_auth/require_config.bench.js +24 -0
- package/dist/src/firebase_auth/require_config.d.ts +8 -0
- package/dist/src/firebase_auth/require_config.js +13 -0
- package/dist/src/firebase_auth/server/auth_user_server_provider.d.ts +24 -0
- package/dist/src/firebase_auth/server/auth_user_server_provider.js +51 -0
- package/dist/src/firebase_auth/server/firebase_server.bench.d.ts +1 -0
- package/dist/src/firebase_auth/server/firebase_server.bench.js +27 -0
- package/dist/src/firebase_auth/server/firebase_server.d.ts +15 -0
- package/dist/src/firebase_auth/server/firebase_server.js +50 -0
- package/dist/src/firebase_auth/server/use_auth_user_server.bench.d.ts +1 -0
- package/dist/src/firebase_auth/server/use_auth_user_server.bench.js +14 -0
- package/dist/src/firebase_auth/server/use_auth_user_server.d.ts +19 -0
- package/dist/src/firebase_auth/server/use_auth_user_server.js +19 -0
- package/dist/src/firebase_auth/types.d.ts +22 -0
- package/dist/src/firebase_auth/types.js +1 -0
- package/dist/src/general/cache_variables.bench.d.ts +1 -0
- package/dist/src/general/cache_variables.bench.js +19 -0
- package/dist/src/general/cache_variables.d.ts +1 -0
- package/dist/src/general/cache_variables.js +4 -6
- package/dist/src/general/general_functions.bench.d.ts +1 -0
- package/dist/src/general/general_functions.bench.js +18 -0
- package/dist/src/general/general_functions.js +4 -0
- package/dist/src/general/index.js +0 -1
- package/dist/src/general/metadata.bench.d.ts +1 -0
- package/dist/src/general/metadata.bench.js +15 -0
- package/dist/src/general/metadata.js +1 -1
- package/dist/src/server/components/server_provider.js +7 -1
- package/dist/src/server/functions/get_user_locale.bench.d.ts +1 -0
- package/dist/src/server/functions/get_user_locale.bench.js +13 -0
- package/dist/src/server/functions/get_user_locale.js +7 -4
- package/dist/src/server/functions/server.bench.d.ts +1 -0
- package/dist/src/server/functions/server.bench.js +39 -0
- package/dist/src/server/functions/server.js +9 -5
- package/dist/src/server/functions/use_functions.bench.d.ts +1 -0
- package/dist/src/server/functions/use_functions.bench.js +23 -0
- package/dist/src/server/functions/use_functions.d.ts +5 -0
- package/dist/src/server/functions/use_functions.js +19 -4
- package/dist/src/server/index.js +4 -1
- package/dist/src/test_utils/mock_next_server.d.ts +5 -0
- package/dist/src/test_utils/mock_next_server.js +10 -0
- package/dist/src/types/types.d.ts +48 -1
- package/llms.txt +47 -0
- package/package.json +46 -8
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useCallback, useEffect, useRef, useState } from 'react';
|
|
4
|
+
import { useRouter } from 'next/navigation';
|
|
5
|
+
import usePathname from '../../client/hooks/use_path_name';
|
|
6
|
+
import config from '@intl-config';
|
|
7
|
+
import requireFirebaseAuthConfig from '../require_config';
|
|
8
|
+
import { getFirebaseAuthClient } from './firebase_client';
|
|
9
|
+
import { setAuthUserCache } from './auth_user_cache';
|
|
10
|
+
import { sessionCookieName } from '../middleware/update_session';
|
|
11
|
+
// `null` default (instead of a `{ loading: true, ... }` stand-in) lets
|
|
12
|
+
// `useAuthUser` distinguish "not wrapped in AuthUserProvider" (throw) from
|
|
13
|
+
// "wrapped, still loading" (`loading: true`).
|
|
14
|
+
export const AuthUserContext = createContext(null);
|
|
15
|
+
function writeSessionCookie(idToken, maxAge) {
|
|
16
|
+
document.cookie = `${sessionCookieName}=${idToken}; path=/; max-age=${maxAge}`;
|
|
17
|
+
}
|
|
18
|
+
function clearSessionCookie() {
|
|
19
|
+
document.cookie = `${sessionCookieName}=; path=/; max-age=0`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Client-side auth-state provider for `firebase_auth`. Wrap your root layout
|
|
23
|
+
* (or a client boundary below it) with this to make `useAuthUser()`
|
|
24
|
+
* (`cloudflare-next-intl/useFirebaseAuthUser`, client variant) resolve
|
|
25
|
+
* `{ user, loading }` from the live Firebase `onIdTokenChanged` listener,
|
|
26
|
+
* and to get automatic session-cookie sync + redirect-on-sign-out/verify
|
|
27
|
+
* behavior driven by `firebaseAuth.isAuthPath` / `whiteListPaths` /
|
|
28
|
+
* `redirectAuthPath` / `verifyEmailPath` on your `RoutingConfig`.
|
|
29
|
+
*
|
|
30
|
+
* Requires `firebaseAuth` to be set on the config passed to `setIntlConfig`
|
|
31
|
+
* — throws via {@link requireFirebaseAuthConfig} otherwise.
|
|
32
|
+
*
|
|
33
|
+
* @param initialUser Server-resolved user (e.g. from
|
|
34
|
+
* `useFirebaseAuthUser`'s `react-server` variant) to avoid a
|
|
35
|
+
* loading flash on first paint; pass `null`/omit if unavailable.
|
|
36
|
+
* @example
|
|
37
|
+
* <AuthUserProvider initialUser={initialUser}>{children}</AuthUserProvider>
|
|
38
|
+
*/
|
|
39
|
+
export default function AuthUserProvider({ initialUser = null, children }) {
|
|
40
|
+
const fa = config.firebaseAuth;
|
|
41
|
+
requireFirebaseAuthConfig(fa);
|
|
42
|
+
const router = useRouter();
|
|
43
|
+
const pathname = usePathname();
|
|
44
|
+
const isAuthPage = fa.isAuthPath(pathname);
|
|
45
|
+
const isWhiteListed = fa.whiteListPaths?.includes(pathname) ?? false;
|
|
46
|
+
const maxAge = fa.sessionCookieMaxAge ?? 60 * 60 * 24 * 5;
|
|
47
|
+
const [state, setState] = useState({
|
|
48
|
+
user: initialUser,
|
|
49
|
+
loading: initialUser === null,
|
|
50
|
+
});
|
|
51
|
+
const syncedSignedIn = useRef(undefined);
|
|
52
|
+
const consecutiveNulls = useRef(0);
|
|
53
|
+
const [confirmedSignedOut, setConfirmedSignedOut] = useState(initialUser === null);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const { user, loading } = state;
|
|
56
|
+
if (loading || isAuthPage || isWhiteListed)
|
|
57
|
+
return;
|
|
58
|
+
if (!user) {
|
|
59
|
+
if (confirmedSignedOut)
|
|
60
|
+
router.replace(fa.redirectAuthPath);
|
|
61
|
+
}
|
|
62
|
+
else if (fa.verifyEmailPath && !user.emailVerified && pathname !== fa.verifyEmailPath) {
|
|
63
|
+
router.replace(fa.verifyEmailPath);
|
|
64
|
+
}
|
|
65
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
66
|
+
}, [state, pathname, isAuthPage, isWhiteListed, confirmedSignedOut]);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
let unsubscribe;
|
|
69
|
+
let cancelled = false;
|
|
70
|
+
getFirebaseAuthClient().then(async ({ auth }) => {
|
|
71
|
+
if (cancelled)
|
|
72
|
+
return;
|
|
73
|
+
const { onIdTokenChanged } = await import('firebase/auth');
|
|
74
|
+
unsubscribe = onIdTokenChanged(auth, async (user) => {
|
|
75
|
+
const isSignedIn = !!user;
|
|
76
|
+
const previous = syncedSignedIn.current;
|
|
77
|
+
try {
|
|
78
|
+
if (user) {
|
|
79
|
+
writeSessionCookie(await user.getIdToken(true), maxAge);
|
|
80
|
+
}
|
|
81
|
+
else if (previous) {
|
|
82
|
+
clearSessionCookie();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
catch (e) {
|
|
86
|
+
console.error('AuthUserProvider: session sync failed', e);
|
|
87
|
+
setAuthUserCache(user);
|
|
88
|
+
setState({ user, loading: false });
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
syncedSignedIn.current = isSignedIn;
|
|
92
|
+
setAuthUserCache(user);
|
|
93
|
+
setState({ user, loading: false });
|
|
94
|
+
if (user) {
|
|
95
|
+
consecutiveNulls.current = 0;
|
|
96
|
+
setConfirmedSignedOut(false);
|
|
97
|
+
}
|
|
98
|
+
else {
|
|
99
|
+
consecutiveNulls.current += 1;
|
|
100
|
+
if (consecutiveNulls.current >= 2)
|
|
101
|
+
setConfirmedSignedOut(true);
|
|
102
|
+
}
|
|
103
|
+
const flipped = previous !== undefined && previous !== isSignedIn;
|
|
104
|
+
const contradictsPage = previous === undefined && isSignedIn === isAuthPage;
|
|
105
|
+
if (flipped || contradictsPage) {
|
|
106
|
+
router.refresh();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
return () => {
|
|
111
|
+
cancelled = true;
|
|
112
|
+
unsubscribe?.();
|
|
113
|
+
};
|
|
114
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
115
|
+
}, [router, isAuthPage, maxAge]);
|
|
116
|
+
const reloadUser = useCallback(async () => {
|
|
117
|
+
const { auth } = await getFirebaseAuthClient();
|
|
118
|
+
const user = auth.currentUser;
|
|
119
|
+
if (!user)
|
|
120
|
+
return;
|
|
121
|
+
const { reload } = await import('firebase/auth');
|
|
122
|
+
await reload(user);
|
|
123
|
+
writeSessionCookie(await user.getIdToken(true), maxAge);
|
|
124
|
+
setAuthUserCache(user);
|
|
125
|
+
setState({ user, loading: false });
|
|
126
|
+
}, [maxAge]);
|
|
127
|
+
const sendVerificationEmail = useCallback(async () => {
|
|
128
|
+
const { auth } = await getFirebaseAuthClient();
|
|
129
|
+
const user = auth.currentUser;
|
|
130
|
+
if (!user)
|
|
131
|
+
return;
|
|
132
|
+
const { sendEmailVerification } = await import('firebase/auth');
|
|
133
|
+
await sendEmailVerification(user);
|
|
134
|
+
}, []);
|
|
135
|
+
const logout = useCallback(async () => {
|
|
136
|
+
try {
|
|
137
|
+
const { auth } = await getFirebaseAuthClient();
|
|
138
|
+
const { signOut } = await import('firebase/auth');
|
|
139
|
+
await signOut(auth);
|
|
140
|
+
}
|
|
141
|
+
finally {
|
|
142
|
+
clearSessionCookie();
|
|
143
|
+
window.location.assign(fa.redirectAuthPath);
|
|
144
|
+
}
|
|
145
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
146
|
+
}, [fa.redirectAuthPath]);
|
|
147
|
+
return _jsx(AuthUserContext.Provider, { value: { ...state, reloadUser, sendVerificationEmail, logout }, children: children });
|
|
148
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { bench, describe, vi } from 'vitest';
|
|
2
|
+
vi.mock('@intl-config', () => ({
|
|
3
|
+
default: {
|
|
4
|
+
firebaseAuth: {
|
|
5
|
+
apiKey: 'bench-key',
|
|
6
|
+
authDomain: 'domain',
|
|
7
|
+
projectId: 'proj',
|
|
8
|
+
appId: 'app',
|
|
9
|
+
redirectAuthPath: '/login',
|
|
10
|
+
homePath: '/',
|
|
11
|
+
isAuthPath: () => false,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
}));
|
|
15
|
+
vi.mock('firebase/app', () => ({
|
|
16
|
+
initializeApp: vi.fn(() => ({ name: 'bench-app' })),
|
|
17
|
+
getApps: vi.fn(() => []),
|
|
18
|
+
getApp: vi.fn(() => ({ name: 'bench-app' })),
|
|
19
|
+
}));
|
|
20
|
+
vi.mock('firebase/auth', () => ({
|
|
21
|
+
getAuth: vi.fn(() => ({ currentUser: null })),
|
|
22
|
+
}));
|
|
23
|
+
describe('getFirebaseAuthClient', () => {
|
|
24
|
+
bench('warm: cached client, no dynamic import', async () => {
|
|
25
|
+
const { getFirebaseAuthClient } = await import('./firebase_client');
|
|
26
|
+
await getFirebaseAuthClient();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { FirebaseApp } from 'firebase/app';
|
|
2
|
+
import type { Auth } from 'firebase/auth';
|
|
3
|
+
/**
|
|
4
|
+
* Lazily loads and initializes `firebase/app`/`firebase/auth` — a dynamic
|
|
5
|
+
* import, not a static one, so consumers who never call a firebase_auth
|
|
6
|
+
* export never pull these packages into their bundle or runtime at all.
|
|
7
|
+
* Throws if `firebaseAuth` is missing from `RoutingConfig` (see
|
|
8
|
+
* `require_config.ts`) instead of silently no-op'ing.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getFirebaseAuthClient(): Promise<{
|
|
11
|
+
app: FirebaseApp;
|
|
12
|
+
auth: Auth;
|
|
13
|
+
}>;
|
|
14
|
+
/** Synchronous read of the cached client, or `undefined` before the first `getFirebaseAuthClient()` resolves. */
|
|
15
|
+
export declare function getFirebaseAuthClientSync(): {
|
|
16
|
+
app: FirebaseApp;
|
|
17
|
+
auth: Auth;
|
|
18
|
+
} | undefined;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import config from '@intl-config';
|
|
3
|
+
import requireFirebaseAuthConfig from '../require_config';
|
|
4
|
+
let cached;
|
|
5
|
+
let cachedPromise;
|
|
6
|
+
/**
|
|
7
|
+
* Lazily loads and initializes `firebase/app`/`firebase/auth` — a dynamic
|
|
8
|
+
* import, not a static one, so consumers who never call a firebase_auth
|
|
9
|
+
* export never pull these packages into their bundle or runtime at all.
|
|
10
|
+
* Throws if `firebaseAuth` is missing from `RoutingConfig` (see
|
|
11
|
+
* `require_config.ts`) instead of silently no-op'ing.
|
|
12
|
+
*/
|
|
13
|
+
export async function getFirebaseAuthClient() {
|
|
14
|
+
requireFirebaseAuthConfig(config.firebaseAuth);
|
|
15
|
+
if (cached)
|
|
16
|
+
return cached;
|
|
17
|
+
if (!cachedPromise) {
|
|
18
|
+
const fa = config.firebaseAuth;
|
|
19
|
+
cachedPromise = Promise.all([import('firebase/app'), import('firebase/auth')]).then(([{ getApp, getApps, initializeApp }, { getAuth }]) => {
|
|
20
|
+
const firebaseConfig = {
|
|
21
|
+
apiKey: fa.apiKey,
|
|
22
|
+
authDomain: fa.authDomain,
|
|
23
|
+
projectId: fa.projectId,
|
|
24
|
+
storageBucket: fa.storageBucket,
|
|
25
|
+
messagingSenderId: fa.messagingSenderId,
|
|
26
|
+
appId: fa.appId,
|
|
27
|
+
measurementId: fa.measurementId,
|
|
28
|
+
};
|
|
29
|
+
const app = getApps().length ? getApp() : initializeApp(firebaseConfig);
|
|
30
|
+
const auth = getAuth(app);
|
|
31
|
+
cached = { app, auth };
|
|
32
|
+
return cached;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return cachedPromise;
|
|
36
|
+
}
|
|
37
|
+
/** Synchronous read of the cached client, or `undefined` before the first `getFirebaseAuthClient()` resolves. */
|
|
38
|
+
export function getFirebaseAuthClientSync() {
|
|
39
|
+
return cached;
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AuthUserContextType } from './auth_user_provider';
|
|
2
|
+
/**
|
|
3
|
+
* Reads the current Firebase auth user and its actions from
|
|
4
|
+
* `AuthUserProvider`'s context.
|
|
5
|
+
*
|
|
6
|
+
* @throws If called without an `AuthUserProvider` above it in the tree.
|
|
7
|
+
* @example
|
|
8
|
+
* const { user, loading, logout } = useAuthUser();
|
|
9
|
+
* if (loading) return null;
|
|
10
|
+
* return user ? <button onClick={logout}>Log out</button> : null;
|
|
11
|
+
*/
|
|
12
|
+
export default function useAuthUser(): AuthUserContextType;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useContext } from 'react';
|
|
3
|
+
import { AuthUserContext } from './auth_user_provider';
|
|
4
|
+
/**
|
|
5
|
+
* Reads the current Firebase auth user and its actions from
|
|
6
|
+
* `AuthUserProvider`'s context.
|
|
7
|
+
*
|
|
8
|
+
* @throws If called without an `AuthUserProvider` above it in the tree.
|
|
9
|
+
* @example
|
|
10
|
+
* const { user, loading, logout } = useAuthUser();
|
|
11
|
+
* if (loading) return null;
|
|
12
|
+
* return user ? <button onClick={logout}>Log out</button> : null;
|
|
13
|
+
*/
|
|
14
|
+
export default function useAuthUser() {
|
|
15
|
+
const context = useContext(AuthUserContext);
|
|
16
|
+
if (context === null) {
|
|
17
|
+
throw new Error('useAuthUser must be used within an AuthUserProvider');
|
|
18
|
+
}
|
|
19
|
+
return context;
|
|
20
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { bench, describe } from 'vitest';
|
|
2
|
+
import { DEFAULT_MESSAGES_EN } from './default_messages.en';
|
|
3
|
+
describe('DEFAULT_MESSAGES_EN', () => {
|
|
4
|
+
bench('reads a known key from the message map', () => {
|
|
5
|
+
void DEFAULT_MESSAGES_EN.invalidEmail;
|
|
6
|
+
});
|
|
7
|
+
bench('reads the unknown fallback key', () => {
|
|
8
|
+
void DEFAULT_MESSAGES_EN.unknown;
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const DEFAULT_MESSAGES_EN: Record<string, string>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DEFAULT_MESSAGES_EN = {
|
|
2
|
+
invalidEmail: 'Please enter a valid email address.',
|
|
3
|
+
userDisabled: 'This account has been disabled.',
|
|
4
|
+
invalidCredential: 'Invalid email or password.',
|
|
5
|
+
emailAlreadyInUse: 'An account with this email already exists.',
|
|
6
|
+
weakPassword: 'Password is too weak.',
|
|
7
|
+
tooManyRequests: 'Too many attempts. Please try again later.',
|
|
8
|
+
networkRequestFailed: 'Network error. Please check your connection.',
|
|
9
|
+
requiresRecentLogin: 'Please log in again to continue.',
|
|
10
|
+
expiredActionCode: 'This link has expired. Please request a new one.',
|
|
11
|
+
invalidActionCode: 'This link is invalid or has already been used.',
|
|
12
|
+
userTokenExpired: 'Your session has expired. Please log in again.',
|
|
13
|
+
unknown: 'Something went wrong. Please try again.',
|
|
14
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { bench, describe } from 'vitest';
|
|
2
|
+
import firebaseAuthErrorMessage from './firebase_auth_error_helper';
|
|
3
|
+
import { setMessageForLocaleCache } from '../../general/cache_variables';
|
|
4
|
+
describe('firebaseAuthErrorMessage', () => {
|
|
5
|
+
bench('no message cache for locale: falls back to English default', () => {
|
|
6
|
+
firebaseAuthErrorMessage('de', { code: 'auth/invalid-email' });
|
|
7
|
+
});
|
|
8
|
+
bench('unrecognized error code: maps to unknown key', () => {
|
|
9
|
+
firebaseAuthErrorMessage('en', { code: 'auth/something-else' });
|
|
10
|
+
});
|
|
11
|
+
setMessageForLocaleCache('fr', { firebaseAuth: { invalidEmail: 'E-mail invalide.' } });
|
|
12
|
+
bench('locale translation present: uses the cached message', () => {
|
|
13
|
+
firebaseAuthErrorMessage('fr', { code: 'auth/invalid-email' });
|
|
14
|
+
});
|
|
15
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves a Firebase auth error to a user-facing message. If the consumer's
|
|
3
|
+
* locale messages have a `firebaseAuth` namespace with a matching key, that
|
|
4
|
+
* translation is used; otherwise falls back to the bundled English default.
|
|
5
|
+
*/
|
|
6
|
+
export default function firebaseAuthErrorMessage(locale: string, error: unknown): string;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getTranslationsImpl } from '../../general/general_functions';
|
|
2
|
+
import { getMessageCache } from '../../general/cache_variables';
|
|
3
|
+
import { DEFAULT_MESSAGES_EN } from './default_messages.en';
|
|
4
|
+
const ERROR_CODE_TO_KEY = {
|
|
5
|
+
'auth/invalid-email': 'invalidEmail',
|
|
6
|
+
'auth/user-disabled': 'userDisabled',
|
|
7
|
+
'auth/user-not-found': 'invalidCredential',
|
|
8
|
+
'auth/wrong-password': 'invalidCredential',
|
|
9
|
+
'auth/invalid-credential': 'invalidCredential',
|
|
10
|
+
'auth/email-already-in-use': 'emailAlreadyInUse',
|
|
11
|
+
'auth/weak-password': 'weakPassword',
|
|
12
|
+
'auth/too-many-requests': 'tooManyRequests',
|
|
13
|
+
'auth/network-request-failed': 'networkRequestFailed',
|
|
14
|
+
'auth/requires-recent-login': 'requiresRecentLogin',
|
|
15
|
+
'auth/expired-action-code': 'expiredActionCode',
|
|
16
|
+
'auth/invalid-action-code': 'invalidActionCode',
|
|
17
|
+
'auth/user-token-expired': 'userTokenExpired',
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Resolves a Firebase auth error to a user-facing message. If the consumer's
|
|
21
|
+
* locale messages have a `firebaseAuth` namespace with a matching key, that
|
|
22
|
+
* translation is used; otherwise falls back to the bundled English default.
|
|
23
|
+
*/
|
|
24
|
+
export default function firebaseAuthErrorMessage(locale, error) {
|
|
25
|
+
const code = typeof error === 'object' && error !== null && 'code' in error
|
|
26
|
+
? String(error.code)
|
|
27
|
+
: '';
|
|
28
|
+
const key = ERROR_CODE_TO_KEY[code] ?? 'unknown';
|
|
29
|
+
const messages = getMessageCache(locale);
|
|
30
|
+
if (messages) {
|
|
31
|
+
try {
|
|
32
|
+
const t = getTranslationsImpl(locale, messages, 'firebaseAuth');
|
|
33
|
+
const translated = t(key);
|
|
34
|
+
if (typeof translated === 'string' && translated !== key)
|
|
35
|
+
return translated;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// fall through to English default
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return DEFAULT_MESSAGES_EN[key] ?? DEFAULT_MESSAGES_EN.unknown;
|
|
42
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { default as FirebaseAuthClientProvider } from './client/auth_user_provider';
|
|
2
|
+
export { default as FirebaseAuthServerProvider } from './server/auth_user_server_provider';
|
|
3
|
+
export { default as useFirebaseAuthUserClient } from './client/use_auth_user';
|
|
4
|
+
export { default as useFirebaseAuthUserServer } from './server/use_auth_user_server';
|
|
5
|
+
export { createLoginAction, createSignUpAction, createForgotPasswordAction } from './client/auth_actions';
|
|
6
|
+
export { default as updateFirebaseAuthSession, sessionCookieName as firebaseAuthSessionCookieName } from './middleware/update_session';
|
|
7
|
+
export { getFirebaseAuthClient } from './client/firebase_client';
|
|
8
|
+
export type { SerializedAuthUser, AuthFormState, AuthActionMessages, AuthUser } from './types';
|
|
9
|
+
export type { FirebaseAuthRoutingConfig } from '../types/types';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { default as FirebaseAuthClientProvider } from './client/auth_user_provider';
|
|
2
|
+
export { default as FirebaseAuthServerProvider } from './server/auth_user_server_provider';
|
|
3
|
+
// NOTE: no single `useFirebaseAuthUser` re-export here — the barrel is a
|
|
4
|
+
// plain module graph with no bundler-conditional resolution, so it cannot
|
|
5
|
+
// replicate the react-server/default split package.json's exports map
|
|
6
|
+
// provides for the ./useFirebaseAuthUser subpath. A consumer wanting the
|
|
7
|
+
// hook imports `cloudflare-next-intl/useFirebaseAuthUser` directly (which
|
|
8
|
+
// DOES resolve correctly per-environment) rather than through this barrel.
|
|
9
|
+
export { default as useFirebaseAuthUserClient } from './client/use_auth_user';
|
|
10
|
+
export { default as useFirebaseAuthUserServer } from './server/use_auth_user_server';
|
|
11
|
+
export { createLoginAction, createSignUpAction, createForgotPasswordAction } from './client/auth_actions';
|
|
12
|
+
export { default as updateFirebaseAuthSession, sessionCookieName as firebaseAuthSessionCookieName } from './middleware/update_session';
|
|
13
|
+
export { getFirebaseAuthClient } from './client/firebase_client';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @vitest-environment node
|
|
2
|
+
import { bench, describe, vi } from 'vitest';
|
|
3
|
+
import { NextResponse } from 'next/server';
|
|
4
|
+
import { makeTestRequest } from '../../test_utils/mock_next_server';
|
|
5
|
+
const baseFa = {
|
|
6
|
+
apiKey: 'bench-api-key',
|
|
7
|
+
authDomain: 'domain',
|
|
8
|
+
projectId: 'proj',
|
|
9
|
+
appId: 'app',
|
|
10
|
+
redirectAuthPath: '/login',
|
|
11
|
+
homePath: '/',
|
|
12
|
+
isAuthPath: (path) => path === '/login',
|
|
13
|
+
};
|
|
14
|
+
vi.mock('@intl-config', () => ({
|
|
15
|
+
default: { locales: ['en'], defaultLocale: 'en', firebaseAuth: baseFa },
|
|
16
|
+
}));
|
|
17
|
+
function makeJwt(exp) {
|
|
18
|
+
const header = Buffer.from(JSON.stringify({ alg: 'none' })).toString('base64url');
|
|
19
|
+
const payload = Buffer.from(JSON.stringify({ exp })).toString('base64url');
|
|
20
|
+
return `${header}.${payload}.sig`;
|
|
21
|
+
}
|
|
22
|
+
describe('updateSession', () => {
|
|
23
|
+
bench('valid, unexpired session cookie: isJwtExpired parse + pass-through', async () => {
|
|
24
|
+
const { default: updateSession } = await import('./update_session');
|
|
25
|
+
const req = makeTestRequest('https://example.com/en/dashboard', {
|
|
26
|
+
cookies: { __fa_session__: makeJwt(Date.now() / 1000 + 3600) },
|
|
27
|
+
});
|
|
28
|
+
await updateSession(req, NextResponse.next(), 'en');
|
|
29
|
+
});
|
|
30
|
+
bench('malformed session cookie: isJwtExpired parse failure path', async () => {
|
|
31
|
+
const { default: updateSession } = await import('./update_session');
|
|
32
|
+
const req = makeTestRequest('https://example.com/en/dashboard', {
|
|
33
|
+
cookies: { __fa_session__: 'not.valid.jwt' },
|
|
34
|
+
});
|
|
35
|
+
await updateSession(req, NextResponse.next(), 'en');
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { NextResponse, type NextRequest } from 'next/server';
|
|
2
|
+
export declare const sessionCookieName = "__fa_session__";
|
|
3
|
+
export declare const refreshTokenCookieName = "__fa_refresh_token__";
|
|
4
|
+
/**
|
|
5
|
+
* Layers Firebase session-cookie validation/refresh and auth redirects onto
|
|
6
|
+
* an already-built middleware response. Called internally by `intlMiddleware`
|
|
7
|
+
* (via dynamic import) when `config.firebaseAuth` is set and
|
|
8
|
+
* `middlewareEnabled !== false` — not intended to be called directly unless
|
|
9
|
+
* that auto-wiring is opted out of.
|
|
10
|
+
*
|
|
11
|
+
* @param baseResponse The response `intlMiddleware` already produced for
|
|
12
|
+
* locale routing (its own next()/rewrite()/redirect(), with the locale
|
|
13
|
+
* cookie / bot cookie / `Content-Language` header already set). On the
|
|
14
|
+
* pass-through path this function returns `baseResponse` itself (with
|
|
15
|
+
* Firebase cookies layered on) so none of that is dropped; on the
|
|
16
|
+
* guest/auth-page redirect paths it returns a NEW response instead, since
|
|
17
|
+
* a redirect response can't also carry forward a rewrite/next decision —
|
|
18
|
+
* its cookies/headers are copied across from `baseResponse` so locale
|
|
19
|
+
* state still survives the redirect.
|
|
20
|
+
* @param locale The effective locale `intlMiddleware` resolved for this request.
|
|
21
|
+
*/
|
|
22
|
+
export default function updateSession(request: NextRequest, baseResponse: NextResponse, locale: string): Promise<NextResponse>;
|