@delmaredigital/payload-better-auth 0.6.9 → 0.7.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 +15 -4
- package/dist/components/BeforeLogin.d.ts +2 -2
- package/dist/components/BeforeLogin.js +6 -3
- package/dist/components/LoginView.js +7 -4
- package/dist/components/LogoutButton.js +6 -3
- package/dist/components/auth/ForgotPasswordView.js +5 -2
- package/dist/components/auth/ResetPasswordView.d.ts +1 -1
- package/dist/components/auth/ResetPasswordView.js +6 -3
- package/dist/components/management/SecurityNavLinks.js +5 -3
- package/dist/components/twoFactor/TwoFactorSetupView.d.ts +1 -1
- package/dist/components/twoFactor/TwoFactorSetupView.js +10 -5
- package/dist/components/twoFactor/TwoFactorVerifyView.d.ts +1 -1
- package/dist/components/twoFactor/TwoFactorVerifyView.js +6 -3
- package/dist/exports/client.d.ts +9 -1685
- package/dist/exports/client.js +4 -0
- package/dist/generated-types.d.ts +53 -20
- package/dist/scripts/generate-types.js +5 -3
- package/package.json +15 -14
package/README.md
CHANGED
|
@@ -12,7 +12,13 @@ Better Auth adapter and plugins for Payload CMS. Enables seamless integration be
|
|
|
12
12
|
<a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fdelmaredigital%2Fdd-starter&project-name=my-payload-site&build-command=pnpm%20run%20ci&env=PAYLOAD_SECRET,BETTER_AUTH_SECRET&stores=%5B%7B%22type%22%3A%22integration%22%2C%22protocol%22%3A%22storage%22%2C%22productSlug%22%3A%22neon%22%2C%22integrationSlug%22%3A%22neon%22%7D%2C%7B%22type%22%3A%22blob%22%7D%5D"><img src="https://vercel.com/button" alt="Deploy with Vercel" height="32"></a>
|
|
13
13
|
</p>
|
|
14
14
|
|
|
15
|
-
> **Upgrading to 0.
|
|
15
|
+
> ⚠️ **Upgrading to 0.7?** This release requires **Better Auth 1.6** and includes several breaking changes:
|
|
16
|
+
>
|
|
17
|
+
> - **Schema migration required** for projects using the `twoFactor` plugin — Better Auth 1.6.2 added a `verified` column to the `twoFactor` table.
|
|
18
|
+
> - **`oidcProvider` → `@better-auth/oauth-provider`** — generated OAuth types now reflect the `oauth-provider` schema. Consumers using `oidcProvider()` at runtime will keep working, but `OauthApplication` / `PluginId` / `ModelKey` type exports have changed shape. Migrating to `@better-auth/oauth-provider` is recommended.
|
|
19
|
+
> - **Client helper type widening** — `createPayloadAuthClient()` and `payloadAuthPlugins` are typed more conservatively to keep `.d.ts` portable. For typed plugin methods (e.g. `client.twoFactor.verifyTotp`), list plugins explicitly in `createAuthClient({ plugins: [...] })` (see [Client-Side Auth](#4-client-side-auth) below).
|
|
20
|
+
>
|
|
21
|
+
> See the [CHANGELOG](./CHANGELOG.md#070---2026-04-21) for full migration instructions.
|
|
16
22
|
|
|
17
23
|
---
|
|
18
24
|
|
|
@@ -30,7 +36,7 @@ For AI-assisted exploration: [DeepWiki](https://deepwiki.com/delmaredigital/payl
|
|
|
30
36
|
pnpm add @delmaredigital/payload-better-auth better-auth
|
|
31
37
|
```
|
|
32
38
|
|
|
33
|
-
**Requirements:** `payload` >= 3.69.0 · `better-auth` >= 1.
|
|
39
|
+
**Requirements:** `payload` >= 3.69.0 · `better-auth` >= 1.6.0 · `next` >= 15.4.8 · `react` >= 19.2.1
|
|
34
40
|
|
|
35
41
|
## Quick Start
|
|
36
42
|
|
|
@@ -138,13 +144,18 @@ export default buildConfig({
|
|
|
138
144
|
// src/lib/auth/client.ts
|
|
139
145
|
'use client'
|
|
140
146
|
|
|
141
|
-
import {
|
|
147
|
+
import { createAuthClient, twoFactorClient } from '@delmaredigital/payload-better-auth/client'
|
|
148
|
+
import { passkeyClient } from '@better-auth/passkey/client'
|
|
142
149
|
|
|
143
|
-
export const authClient =
|
|
150
|
+
export const authClient = createAuthClient({
|
|
151
|
+
plugins: [twoFactorClient(), passkeyClient()],
|
|
152
|
+
})
|
|
144
153
|
|
|
145
154
|
export const { useSession, signIn, signUp, signOut, twoFactor, passkey } = authClient
|
|
146
155
|
```
|
|
147
156
|
|
|
157
|
+
> Listing plugins inline (rather than using `createPayloadAuthClient()` or spreading `payloadAuthPlugins`) ensures `twoFactor` and other plugin methods are typed on the returned client.
|
|
158
|
+
|
|
148
159
|
### 5. Server-Side Session
|
|
149
160
|
|
|
150
161
|
```ts
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
export type BeforeLoginProps = {
|
|
2
|
-
/** URL to redirect to for login.
|
|
2
|
+
/** URL to redirect to for login. Defaults to `${routes.admin}/login`. */
|
|
3
3
|
loginUrl?: string;
|
|
4
4
|
};
|
|
5
5
|
/**
|
|
6
6
|
* BeforeLogin component that redirects to the custom login page.
|
|
7
7
|
* Injected into Payload's beforeLogin slot to intercept default login.
|
|
8
8
|
*/
|
|
9
|
-
export declare function BeforeLogin({ loginUrl }
|
|
9
|
+
export declare function BeforeLogin({ loginUrl }?: BeforeLoginProps): import("react").JSX.Element;
|
|
10
10
|
export default BeforeLogin;
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation.js';
|
|
5
|
+
import { useConfig } from '@payloadcms/ui';
|
|
5
6
|
/**
|
|
6
7
|
* BeforeLogin component that redirects to the custom login page.
|
|
7
8
|
* Injected into Payload's beforeLogin slot to intercept default login.
|
|
8
|
-
*/ export function BeforeLogin({ loginUrl =
|
|
9
|
+
*/ export function BeforeLogin({ loginUrl } = {}) {
|
|
9
10
|
const router = useRouter();
|
|
11
|
+
const { config: { routes: { admin: adminRoute } } } = useConfig();
|
|
12
|
+
const target = loginUrl ?? `${adminRoute}/login`;
|
|
10
13
|
useEffect(()=>{
|
|
11
|
-
router.replace(
|
|
14
|
+
router.replace(target);
|
|
12
15
|
}, [
|
|
13
16
|
router,
|
|
14
|
-
|
|
17
|
+
target
|
|
15
18
|
]);
|
|
16
19
|
// Show loading state while redirecting
|
|
17
20
|
return /*#__PURE__*/ _jsx("div", {
|
|
@@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation.js';
|
|
|
5
5
|
import { createAuthClient } from 'better-auth/react';
|
|
6
6
|
import { twoFactorClient } from 'better-auth/client/plugins';
|
|
7
7
|
import { hasAnyRole, hasAllRoles } from '../utils/access.js';
|
|
8
|
+
import { useConfig } from '@payloadcms/ui';
|
|
8
9
|
/**
|
|
9
10
|
* Check if user has the required role(s)
|
|
10
11
|
*/ function checkUserRoles(user, requiredRole, requireAllRoles) {
|
|
@@ -22,6 +23,8 @@ import { hasAnyRole, hasAllRoles } from '../utils/access.js';
|
|
|
22
23
|
}
|
|
23
24
|
export function LoginView({ authClient: providedClient, logo, title = 'Login', afterLoginPath = '/admin', requiredRole = 'admin', requireAllRoles = false, enablePasskey = 'auto', enableSignUp = 'auto', defaultSignUpRole = 'user', enableForgotPassword = 'auto', resetPasswordUrl }) {
|
|
24
25
|
const router = useRouter();
|
|
26
|
+
// Payload Config
|
|
27
|
+
const { config: { routes: { admin: adminRoute, api: apiRoute } } } = useConfig();
|
|
25
28
|
// View state
|
|
26
29
|
const [viewMode, setViewMode] = useState('login');
|
|
27
30
|
// Form fields
|
|
@@ -91,7 +94,7 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
91
94
|
if (enablePasskey === 'auto') {
|
|
92
95
|
// Check if passkey endpoint exists (GET request)
|
|
93
96
|
// Better Auth passkey routes are at /passkey/* (singular)
|
|
94
|
-
fetch(
|
|
97
|
+
fetch(`${apiRoute}/auth/passkey/generate-authenticate-options`, {
|
|
95
98
|
method: 'GET',
|
|
96
99
|
credentials: 'include'
|
|
97
100
|
}).then((res)=>{
|
|
@@ -111,7 +114,7 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
111
114
|
useEffect(()=>{
|
|
112
115
|
if (enableSignUp === 'auto') {
|
|
113
116
|
// Check if sign-up endpoint exists
|
|
114
|
-
fetch(
|
|
117
|
+
fetch(`${apiRoute}/auth/sign-up/email`, {
|
|
115
118
|
method: 'OPTIONS',
|
|
116
119
|
credentials: 'include'
|
|
117
120
|
}).then((res)=>{
|
|
@@ -131,7 +134,7 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
131
134
|
useEffect(()=>{
|
|
132
135
|
if (enableForgotPassword === 'auto') {
|
|
133
136
|
// Check if request-password-reset endpoint exists
|
|
134
|
-
fetch(
|
|
137
|
+
fetch(`${apiRoute}/auth/request-password-reset`, {
|
|
135
138
|
method: 'OPTIONS',
|
|
136
139
|
credentials: 'include'
|
|
137
140
|
}).then((res)=>{
|
|
@@ -254,7 +257,7 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
254
257
|
const client = await getClient();
|
|
255
258
|
const result = await client.requestPasswordReset({
|
|
256
259
|
email,
|
|
257
|
-
redirectTo: resetPasswordUrl ?? `${window.location.origin}/
|
|
260
|
+
redirectTo: resetPasswordUrl ?? `${window.location.origin}${adminRoute}/reset-password`
|
|
258
261
|
});
|
|
259
262
|
if (result.error) {
|
|
260
263
|
setError(result.error.message ?? 'Failed to send reset email');
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation.js';
|
|
5
|
+
import { useConfig } from '@payloadcms/ui';
|
|
5
6
|
/**
|
|
6
7
|
* Logout button component styled to match Payload's admin nav.
|
|
7
8
|
* Uses Payload's CSS classes and variables for native theme integration.
|
|
@@ -10,6 +11,8 @@ import { useRouter } from 'next/navigation.js';
|
|
|
10
11
|
* clean state when switching between users.
|
|
11
12
|
*/ export function LogoutButton() {
|
|
12
13
|
const router = useRouter();
|
|
14
|
+
// Payload Config
|
|
15
|
+
const { config: { routes: { admin: adminRoute, api: apiRoute } } } = useConfig();
|
|
13
16
|
const [isLoading, setIsLoading] = useState(false);
|
|
14
17
|
async function handleLogout() {
|
|
15
18
|
if (isLoading) return;
|
|
@@ -19,7 +22,7 @@ import { useRouter } from 'next/navigation.js';
|
|
|
19
22
|
// - Better Auth: clears BA session cookie
|
|
20
23
|
// - Payload: clears JWT cookie (payload-token) so useAuth() resets
|
|
21
24
|
await Promise.allSettled([
|
|
22
|
-
fetch(
|
|
25
|
+
fetch(`${apiRoute}/auth/sign-out`, {
|
|
23
26
|
method: 'POST',
|
|
24
27
|
credentials: 'include',
|
|
25
28
|
headers: {
|
|
@@ -27,12 +30,12 @@ import { useRouter } from 'next/navigation.js';
|
|
|
27
30
|
},
|
|
28
31
|
body: JSON.stringify({})
|
|
29
32
|
}),
|
|
30
|
-
fetch(
|
|
33
|
+
fetch(`${apiRoute}/users/logout`, {
|
|
31
34
|
method: 'POST',
|
|
32
35
|
credentials: 'include'
|
|
33
36
|
})
|
|
34
37
|
]);
|
|
35
|
-
router.push(
|
|
38
|
+
router.push(`${adminRoute}/login`);
|
|
36
39
|
} catch (error) {
|
|
37
40
|
console.error('[better-auth] Logout error:', error);
|
|
38
41
|
setIsLoading(false);
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useConfig } from '@payloadcms/ui';
|
|
3
4
|
import { useState } from 'react';
|
|
4
5
|
/**
|
|
5
6
|
* Forgot password page component for requesting a password reset email.
|
|
6
7
|
* Uses Better Auth's forgetPassword endpoint.
|
|
7
8
|
*/ export function ForgotPasswordView({ logo, title = 'Forgot Password', loginPath = '/admin/login', successMessage = 'If an account exists with this email, you will receive a password reset link.' }) {
|
|
9
|
+
// Payload Config
|
|
10
|
+
const { config: { routes: { admin: adminRoute, api: apiRoute } } } = useConfig();
|
|
8
11
|
const [email, setEmail] = useState('');
|
|
9
12
|
const [error, setError] = useState(null);
|
|
10
13
|
const [success, setSuccess] = useState(false);
|
|
@@ -14,7 +17,7 @@ import { useState } from 'react';
|
|
|
14
17
|
setLoading(true);
|
|
15
18
|
setError(null);
|
|
16
19
|
try {
|
|
17
|
-
const response = await fetch(
|
|
20
|
+
const response = await fetch(`${apiRoute}/auth/forget-password`, {
|
|
18
21
|
method: 'POST',
|
|
19
22
|
headers: {
|
|
20
23
|
'Content-Type': 'application/json'
|
|
@@ -22,7 +25,7 @@ import { useState } from 'react';
|
|
|
22
25
|
credentials: 'include',
|
|
23
26
|
body: JSON.stringify({
|
|
24
27
|
email,
|
|
25
|
-
redirectTo: `${window.location.origin}/
|
|
28
|
+
redirectTo: `${window.location.origin}${adminRoute}/reset-password`
|
|
26
29
|
})
|
|
27
30
|
});
|
|
28
31
|
if (response.ok) {
|
|
@@ -3,7 +3,7 @@ export type ResetPasswordViewProps = {
|
|
|
3
3
|
logo?: React.ReactNode;
|
|
4
4
|
/** Page title. Default: 'Reset Password' */
|
|
5
5
|
title?: string;
|
|
6
|
-
/** Path to redirect after successful reset.
|
|
6
|
+
/** Path to redirect after successful reset. Defaults to `${routes.admin}/login`. */
|
|
7
7
|
afterResetPath?: string;
|
|
8
8
|
/** Minimum password length. Default: 8 */
|
|
9
9
|
minPasswordLength?: number;
|
|
@@ -2,13 +2,16 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import { useRouter, useSearchParams } from 'next/navigation.js';
|
|
5
|
+
import { useConfig } from '@payloadcms/ui';
|
|
5
6
|
/**
|
|
6
7
|
* Reset password page component for setting a new password.
|
|
7
8
|
* Expects a token in the URL query parameter.
|
|
8
9
|
* Uses Better Auth's resetPassword endpoint.
|
|
9
|
-
*/ export function ResetPasswordView({ logo, title = 'Reset Password', afterResetPath
|
|
10
|
+
*/ export function ResetPasswordView({ logo, title = 'Reset Password', afterResetPath, minPasswordLength = 8 }) {
|
|
10
11
|
const router = useRouter();
|
|
11
12
|
const searchParams = useSearchParams();
|
|
13
|
+
const { config: { routes: { admin: adminRoute, api: apiRoute } } } = useConfig();
|
|
14
|
+
const resolvedAfterResetPath = afterResetPath ?? `${adminRoute}/login`;
|
|
12
15
|
const [password, setPassword] = useState('');
|
|
13
16
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
14
17
|
const [error, setError] = useState(null);
|
|
@@ -42,7 +45,7 @@ import { useRouter, useSearchParams } from 'next/navigation.js';
|
|
|
42
45
|
}
|
|
43
46
|
setLoading(true);
|
|
44
47
|
try {
|
|
45
|
-
const response = await fetch(
|
|
48
|
+
const response = await fetch(`${apiRoute}/auth/reset-password`, {
|
|
46
49
|
method: 'POST',
|
|
47
50
|
headers: {
|
|
48
51
|
'Content-Type': 'application/json'
|
|
@@ -111,7 +114,7 @@ import { useRouter, useSearchParams } from 'next/navigation.js';
|
|
|
111
114
|
children: "Your password has been successfully reset. You can now log in with your new password."
|
|
112
115
|
}),
|
|
113
116
|
/*#__PURE__*/ _jsx("button", {
|
|
114
|
-
onClick: ()=>router.push(
|
|
117
|
+
onClick: ()=>router.push(resolvedAfterResetPath),
|
|
115
118
|
style: {
|
|
116
119
|
padding: 'calc(var(--base) * 0.75) calc(var(--base) * 1.5)',
|
|
117
120
|
background: 'var(--theme-elevation-800)',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { NavGroup } from '@payloadcms/ui';
|
|
3
|
+
import { NavGroup, useConfig } from '@payloadcms/ui';
|
|
4
4
|
/**
|
|
5
5
|
* Navigation links for security management features.
|
|
6
6
|
* Rendered in admin sidebar via afterNavLinks injection.
|
|
@@ -8,14 +8,16 @@ import { NavGroup } from '@payloadcms/ui';
|
|
|
8
8
|
*
|
|
9
9
|
* Currently only renders API Keys link — 2FA and Passkeys
|
|
10
10
|
* are now embedded as ui fields on the user document.
|
|
11
|
-
*/ export function SecurityNavLinks({ basePath
|
|
11
|
+
*/ export function SecurityNavLinks({ basePath, showApiKeys = true } = {}) {
|
|
12
|
+
const { config: { routes: { admin: adminRoute } } } = useConfig();
|
|
12
13
|
if (!showApiKeys) {
|
|
13
14
|
return null;
|
|
14
15
|
}
|
|
16
|
+
const resolvedBasePath = basePath ?? `${adminRoute}/security`;
|
|
15
17
|
return /*#__PURE__*/ _jsx(NavGroup, {
|
|
16
18
|
label: "Security",
|
|
17
19
|
children: /*#__PURE__*/ _jsx("a", {
|
|
18
|
-
href: `${
|
|
20
|
+
href: `${resolvedBasePath}/api-keys`,
|
|
19
21
|
className: "nav__link",
|
|
20
22
|
children: /*#__PURE__*/ _jsx("span", {
|
|
21
23
|
className: "nav__link-label",
|
|
@@ -3,7 +3,7 @@ export type TwoFactorSetupViewProps = {
|
|
|
3
3
|
logo?: React.ReactNode;
|
|
4
4
|
/** Page title. Default: 'Set Up Two-Factor Authentication' */
|
|
5
5
|
title?: string;
|
|
6
|
-
/** Path to redirect after successful setup.
|
|
6
|
+
/** Path to redirect after successful setup. Defaults to `routes.admin`. */
|
|
7
7
|
afterSetupPath?: string;
|
|
8
8
|
/** Callback after successful setup */
|
|
9
9
|
onSetupComplete?: () => void;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
|
+
import { useConfig } from '@payloadcms/ui';
|
|
4
5
|
/**
|
|
5
6
|
* Two-factor authentication setup component.
|
|
6
7
|
* Displays QR code for TOTP apps and allows verification.
|
|
7
8
|
* Uses Better Auth's twoFactor plugin endpoints.
|
|
8
|
-
*/ export function TwoFactorSetupView({ logo, title = 'Set Up Two-Factor Authentication', afterSetupPath
|
|
9
|
+
*/ export function TwoFactorSetupView({ logo, title = 'Set Up Two-Factor Authentication', afterSetupPath, onSetupComplete }) {
|
|
10
|
+
const { config: { routes: { admin: adminRoute, api: apiRoute } } } = useConfig();
|
|
11
|
+
const resolvedAfterSetupPath = afterSetupPath ?? adminRoute;
|
|
9
12
|
const [step, setStep] = useState('loading');
|
|
10
13
|
const [totpUri, setTotpUri] = useState(null);
|
|
11
14
|
const [secret, setSecret] = useState(null);
|
|
@@ -16,7 +19,7 @@ import { useState, useEffect } from 'react';
|
|
|
16
19
|
useEffect(()=>{
|
|
17
20
|
async function enableTwoFactor() {
|
|
18
21
|
try {
|
|
19
|
-
const response = await fetch(
|
|
22
|
+
const response = await fetch(`${apiRoute}/auth/two-factor/enable`, {
|
|
20
23
|
method: 'POST',
|
|
21
24
|
headers: {
|
|
22
25
|
'Content-Type': 'application/json'
|
|
@@ -41,13 +44,15 @@ import { useState, useEffect } from 'react';
|
|
|
41
44
|
}
|
|
42
45
|
}
|
|
43
46
|
enableTwoFactor();
|
|
44
|
-
}, [
|
|
47
|
+
}, [
|
|
48
|
+
apiRoute
|
|
49
|
+
]);
|
|
45
50
|
async function handleVerify(e) {
|
|
46
51
|
e.preventDefault();
|
|
47
52
|
setLoading(true);
|
|
48
53
|
setError(null);
|
|
49
54
|
try {
|
|
50
|
-
const response = await fetch(
|
|
55
|
+
const response = await fetch(`${apiRoute}/auth/two-factor/verify-totp`, {
|
|
51
56
|
method: 'POST',
|
|
52
57
|
headers: {
|
|
53
58
|
'Content-Type': 'application/json'
|
|
@@ -144,7 +149,7 @@ import { useState, useEffect } from 'react';
|
|
|
144
149
|
children: "Your account is now protected with two-factor authentication."
|
|
145
150
|
}),
|
|
146
151
|
/*#__PURE__*/ _jsx("a", {
|
|
147
|
-
href:
|
|
152
|
+
href: resolvedAfterSetupPath,
|
|
148
153
|
style: {
|
|
149
154
|
display: 'inline-block',
|
|
150
155
|
padding: 'calc(var(--base) * 0.75) calc(var(--base) * 1.5)',
|
|
@@ -3,7 +3,7 @@ export type TwoFactorVerifyViewProps = {
|
|
|
3
3
|
logo?: React.ReactNode;
|
|
4
4
|
/** Page title. Default: 'Two-Factor Authentication' */
|
|
5
5
|
title?: string;
|
|
6
|
-
/** Path to redirect after successful verification.
|
|
6
|
+
/** Path to redirect after successful verification. Defaults to `routes.admin`. */
|
|
7
7
|
afterVerifyPath?: string;
|
|
8
8
|
/** Callback after successful verification */
|
|
9
9
|
onVerifyComplete?: () => void;
|
|
@@ -2,12 +2,15 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation.js';
|
|
5
|
+
import { useConfig } from '@payloadcms/ui';
|
|
5
6
|
/**
|
|
6
7
|
* Two-factor authentication verification component.
|
|
7
8
|
* Used during login flow when 2FA is enabled on the account.
|
|
8
9
|
* Uses Better Auth's twoFactor plugin endpoints.
|
|
9
|
-
*/ export function TwoFactorVerifyView({ logo, title = 'Two-Factor Authentication', afterVerifyPath
|
|
10
|
+
*/ export function TwoFactorVerifyView({ logo, title = 'Two-Factor Authentication', afterVerifyPath, onVerifyComplete }) {
|
|
10
11
|
const router = useRouter();
|
|
12
|
+
const { config: { routes: { admin: adminRoute, api: apiRoute } } } = useConfig();
|
|
13
|
+
const resolvedAfterVerifyPath = afterVerifyPath ?? adminRoute;
|
|
11
14
|
const [code, setCode] = useState('');
|
|
12
15
|
const [error, setError] = useState(null);
|
|
13
16
|
const [loading, setLoading] = useState(false);
|
|
@@ -17,7 +20,7 @@ import { useRouter } from 'next/navigation.js';
|
|
|
17
20
|
setLoading(true);
|
|
18
21
|
setError(null);
|
|
19
22
|
try {
|
|
20
|
-
const endpoint = useBackupCode ?
|
|
23
|
+
const endpoint = useBackupCode ? `${apiRoute}/auth/two-factor/verify-backup-code` : `${apiRoute}/auth/two-factor/verify-totp`;
|
|
21
24
|
const response = await fetch(endpoint, {
|
|
22
25
|
method: 'POST',
|
|
23
26
|
headers: {
|
|
@@ -30,7 +33,7 @@ import { useRouter } from 'next/navigation.js';
|
|
|
30
33
|
});
|
|
31
34
|
if (response.ok) {
|
|
32
35
|
onVerifyComplete?.();
|
|
33
|
-
router.push(
|
|
36
|
+
router.push(resolvedAfterVerifyPath);
|
|
34
37
|
router.refresh();
|
|
35
38
|
} else {
|
|
36
39
|
const data = await response.json().catch(()=>({}));
|