@delmaredigital/payload-better-auth 0.5.1 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/components/LoginView.d.ts +1 -2
- package/dist/components/LoginView.js +24 -10
- package/dist/components/PasskeyRegisterButton.d.ts +1 -2
- package/dist/components/PasskeyRegisterButton.js +18 -3
- package/dist/components/PasskeySignInButton.d.ts +1 -2
- package/dist/components/PasskeySignInButton.js +18 -3
- package/dist/components/management/ApiKeysManagementClient.d.ts +2 -3
- package/dist/components/management/ApiKeysManagementClient.js +17 -5
- package/dist/components/management/PasskeysManagementClient.d.ts +1 -2
- package/dist/components/management/PasskeysManagementClient.js +20 -6
- package/dist/components/management/TwoFactorManagementClient.d.ts +1 -2
- package/dist/exports/client.d.ts +79 -1224
- package/dist/exports/client.js +20 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,6 +12,8 @@ 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.5?** This release requires Better Auth 1.5 and includes breaking changes to client plugins, API key imports, and auth instance types. See the [CHANGELOG](./CHANGELOG.md#053---2026-03-01) for migration instructions.
|
|
16
|
+
|
|
15
17
|
---
|
|
16
18
|
|
|
17
19
|
## Documentation
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { type PayloadAuthClient } from '../exports/client.js';
|
|
2
1
|
export type LoginViewProps = {
|
|
3
2
|
/** Optional pre-configured auth client */
|
|
4
|
-
authClient?:
|
|
3
|
+
authClient?: any;
|
|
5
4
|
/** Custom logo element */
|
|
6
5
|
logo?: React.ReactNode;
|
|
7
6
|
/** Login page title. Default: 'Login' */
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useState, useEffect, useRef } from 'react';
|
|
4
4
|
import { useRouter } from 'next/navigation.js';
|
|
5
|
-
import {
|
|
5
|
+
import { createAuthClient } from 'better-auth/react';
|
|
6
|
+
import { twoFactorClient } from 'better-auth/client/plugins';
|
|
6
7
|
import { hasAnyRole, hasAllRoles } from '../utils/access.js';
|
|
7
8
|
/**
|
|
8
9
|
* Check if user has the required role(s)
|
|
@@ -42,12 +43,25 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
42
43
|
// Two-factor authentication state
|
|
43
44
|
const [totpCode, setTotpCode] = useState('');
|
|
44
45
|
const [totpLoading, setTotpLoading] = useState(false);
|
|
45
|
-
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
47
|
+
const clientRef = useRef(null);
|
|
48
|
+
const getClient = async ()=>{
|
|
49
|
+
if (providedClient) return providedClient;
|
|
50
|
+
if (clientRef.current) return clientRef.current;
|
|
51
|
+
const { passkeyClient } = await import('@better-auth/passkey/client');
|
|
52
|
+
clientRef.current = createAuthClient({
|
|
53
|
+
plugins: [
|
|
54
|
+
twoFactorClient(),
|
|
55
|
+
passkeyClient()
|
|
56
|
+
]
|
|
57
|
+
});
|
|
58
|
+
return clientRef.current;
|
|
59
|
+
};
|
|
46
60
|
// Check if user is already logged in on mount
|
|
47
61
|
useEffect(()=>{
|
|
48
62
|
async function checkSession() {
|
|
49
63
|
try {
|
|
50
|
-
const client = getClient();
|
|
64
|
+
const client = await getClient();
|
|
51
65
|
const result = await client.getSession();
|
|
52
66
|
if (result.data?.user) {
|
|
53
67
|
const user = result.data.user;
|
|
@@ -140,7 +154,7 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
140
154
|
setSuccessMessage(null);
|
|
141
155
|
setAccessDenied(false);
|
|
142
156
|
try {
|
|
143
|
-
const client = getClient();
|
|
157
|
+
const client = await getClient();
|
|
144
158
|
const result = await client.signIn.email({
|
|
145
159
|
email,
|
|
146
160
|
password
|
|
@@ -190,7 +204,7 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
190
204
|
return;
|
|
191
205
|
}
|
|
192
206
|
try {
|
|
193
|
-
const client = getClient();
|
|
207
|
+
const client = await getClient();
|
|
194
208
|
const result = await client.signUp.email({
|
|
195
209
|
email,
|
|
196
210
|
password,
|
|
@@ -237,7 +251,7 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
237
251
|
setError(null);
|
|
238
252
|
setSuccessMessage(null);
|
|
239
253
|
try {
|
|
240
|
-
const client = getClient();
|
|
254
|
+
const client = await getClient();
|
|
241
255
|
const result = await client.requestPasswordReset({
|
|
242
256
|
email,
|
|
243
257
|
redirectTo: resetPasswordUrl ?? `${window.location.origin}/admin/reset-password`
|
|
@@ -260,7 +274,7 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
260
274
|
setTotpLoading(true);
|
|
261
275
|
setError(null);
|
|
262
276
|
try {
|
|
263
|
-
const client = getClient();
|
|
277
|
+
const client = await getClient();
|
|
264
278
|
const result = await client.twoFactor.verifyTotp({
|
|
265
279
|
code: totpCode
|
|
266
280
|
});
|
|
@@ -313,7 +327,7 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
313
327
|
setError(null);
|
|
314
328
|
setAccessDenied(false);
|
|
315
329
|
try {
|
|
316
|
-
const client = getClient();
|
|
330
|
+
const client = await getClient();
|
|
317
331
|
const result = await client.signIn.passkey();
|
|
318
332
|
if (result.error) {
|
|
319
333
|
setError(result.error.message ?? 'Passkey authentication failed');
|
|
@@ -348,7 +362,7 @@ export function LoginView({ authClient: providedClient, logo, title = 'Login', a
|
|
|
348
362
|
}
|
|
349
363
|
}
|
|
350
364
|
async function handleSignOut() {
|
|
351
|
-
const client = getClient();
|
|
365
|
+
const client = await getClient();
|
|
352
366
|
await client.signOut();
|
|
353
367
|
setAccessDenied(false);
|
|
354
368
|
router.refresh();
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { type ButtonHTMLAttributes } from 'react';
|
|
2
|
-
import { type PayloadAuthClient } from '../exports/client.js';
|
|
3
2
|
export type PasskeyRegisterButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> & {
|
|
4
3
|
/** Optional pre-configured auth client */
|
|
5
|
-
authClient?:
|
|
4
|
+
authClient?: any;
|
|
6
5
|
/** Optional name for the passkey */
|
|
7
6
|
passkeyName?: string;
|
|
8
7
|
/** Callback when registration succeeds */
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import {
|
|
3
|
+
import { useState, useRef } from 'react';
|
|
4
|
+
import { createAuthClient } from 'better-auth/react';
|
|
5
|
+
import { twoFactorClient } from 'better-auth/client/plugins';
|
|
5
6
|
/**
|
|
6
7
|
* Standalone passkey registration button component.
|
|
7
8
|
* Handles the WebAuthn registration flow with Better Auth.
|
|
@@ -27,10 +28,24 @@ import { createPayloadAuthClient } from '../exports/client.js';
|
|
|
27
28
|
* ```
|
|
28
29
|
*/ export function PasskeyRegisterButton({ authClient: providedClient, passkeyName, onSuccess, onError, label = 'Add Passkey', loadingLabel = 'Registering...', disabled, children, ...buttonProps }) {
|
|
29
30
|
const [loading, setLoading] = useState(false);
|
|
31
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
32
|
+
const clientRef = useRef(null);
|
|
33
|
+
async function getClient() {
|
|
34
|
+
if (providedClient) return providedClient;
|
|
35
|
+
if (clientRef.current) return clientRef.current;
|
|
36
|
+
const { passkeyClient } = await import('@better-auth/passkey/client');
|
|
37
|
+
clientRef.current = createAuthClient({
|
|
38
|
+
plugins: [
|
|
39
|
+
twoFactorClient(),
|
|
40
|
+
passkeyClient()
|
|
41
|
+
]
|
|
42
|
+
});
|
|
43
|
+
return clientRef.current;
|
|
44
|
+
}
|
|
30
45
|
async function handleClick() {
|
|
31
46
|
setLoading(true);
|
|
32
47
|
try {
|
|
33
|
-
const client =
|
|
48
|
+
const client = await getClient();
|
|
34
49
|
const result = await client.passkey.addPasskey({
|
|
35
50
|
name: passkeyName
|
|
36
51
|
});
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import { type ButtonHTMLAttributes } from 'react';
|
|
2
|
-
import { type PayloadAuthClient } from '../exports/client.js';
|
|
3
2
|
export type PasskeySignInButtonProps = Omit<ButtonHTMLAttributes<HTMLButtonElement>, 'onClick'> & {
|
|
4
3
|
/** Optional pre-configured auth client */
|
|
5
|
-
authClient?:
|
|
4
|
+
authClient?: any;
|
|
6
5
|
/** Callback when sign-in succeeds */
|
|
7
6
|
onSuccess?: (user: {
|
|
8
7
|
id: string;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import {
|
|
3
|
+
import { useState, useRef } from 'react';
|
|
4
|
+
import { createAuthClient } from 'better-auth/react';
|
|
5
|
+
import { twoFactorClient } from 'better-auth/client/plugins';
|
|
5
6
|
/**
|
|
6
7
|
* Standalone passkey sign-in button component.
|
|
7
8
|
* Handles the WebAuthn authentication flow with Better Auth.
|
|
@@ -25,10 +26,24 @@ import { createPayloadAuthClient } from '../exports/client.js';
|
|
|
25
26
|
* ```
|
|
26
27
|
*/ export function PasskeySignInButton({ authClient: providedClient, onSuccess, onError, label = 'Sign in with Passkey', loadingLabel = 'Authenticating...', disabled, children, ...buttonProps }) {
|
|
27
28
|
const [loading, setLoading] = useState(false);
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
30
|
+
const clientRef = useRef(null);
|
|
31
|
+
async function getClient() {
|
|
32
|
+
if (providedClient) return providedClient;
|
|
33
|
+
if (clientRef.current) return clientRef.current;
|
|
34
|
+
const { passkeyClient } = await import('@better-auth/passkey/client');
|
|
35
|
+
clientRef.current = createAuthClient({
|
|
36
|
+
plugins: [
|
|
37
|
+
twoFactorClient(),
|
|
38
|
+
passkeyClient()
|
|
39
|
+
]
|
|
40
|
+
});
|
|
41
|
+
return clientRef.current;
|
|
42
|
+
}
|
|
28
43
|
async function handleClick() {
|
|
29
44
|
setLoading(true);
|
|
30
45
|
try {
|
|
31
|
-
const client =
|
|
46
|
+
const client = await getClient();
|
|
32
47
|
const result = await client.signIn.passkey();
|
|
33
48
|
if (result.error) {
|
|
34
49
|
onError?.(result.error.message ?? 'Passkey authentication failed');
|
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { type PayloadAuthClient } from '../../exports/client.js';
|
|
2
1
|
import type { AvailableScope } from '../../types/apiKey.js';
|
|
3
2
|
export type ApiKeysManagementClientProps = {
|
|
4
|
-
/** Optional pre-configured auth client */
|
|
5
|
-
authClient?:
|
|
3
|
+
/** Optional pre-configured auth client with apiKey plugin */
|
|
4
|
+
authClient?: any;
|
|
6
5
|
/** Page title. Default: 'API Keys' */
|
|
7
6
|
title?: string;
|
|
8
7
|
/** Available scopes for key creation. Auto-generated if not provided. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect, useMemo, useRef } from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { createAuthClient } from 'better-auth/react';
|
|
5
5
|
/**
|
|
6
6
|
* Group scopes by collection for the UI.
|
|
7
7
|
* Scopes like "posts:read", "posts:write" get grouped under "Posts"
|
|
@@ -89,7 +89,19 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
89
89
|
}, [
|
|
90
90
|
scopeGroups
|
|
91
91
|
]);
|
|
92
|
-
|
|
92
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
93
|
+
const clientRef = useRef(null);
|
|
94
|
+
const getClient = async ()=>{
|
|
95
|
+
if (providedClient) return providedClient;
|
|
96
|
+
if (clientRef.current) return clientRef.current;
|
|
97
|
+
const { apiKeyClient } = await import('@better-auth/api-key/client');
|
|
98
|
+
clientRef.current = createAuthClient({
|
|
99
|
+
plugins: [
|
|
100
|
+
apiKeyClient()
|
|
101
|
+
]
|
|
102
|
+
});
|
|
103
|
+
return clientRef.current;
|
|
104
|
+
};
|
|
93
105
|
// Toggle a scope selection
|
|
94
106
|
function toggleScope(scopeId) {
|
|
95
107
|
setSelectedScopes((prev)=>prev.includes(scopeId) ? prev.filter((s)=>s !== scopeId) : [
|
|
@@ -193,7 +205,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
193
205
|
setLoading(true);
|
|
194
206
|
setError(null);
|
|
195
207
|
try {
|
|
196
|
-
const client = getClient();
|
|
208
|
+
const client = await getClient();
|
|
197
209
|
const result = await client.apiKey.list();
|
|
198
210
|
if (result.error) {
|
|
199
211
|
setError(result.error.message ?? 'Failed to load API keys');
|
|
@@ -213,7 +225,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
213
225
|
setError(null);
|
|
214
226
|
setNewlyCreatedKey(null);
|
|
215
227
|
try {
|
|
216
|
-
const client = getClient();
|
|
228
|
+
const client = await getClient();
|
|
217
229
|
// Send scopes to server - server will convert to permissions
|
|
218
230
|
const createOptions = {
|
|
219
231
|
name: newKeyName
|
|
@@ -249,7 +261,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
249
261
|
setDeleting(keyId);
|
|
250
262
|
setError(null);
|
|
251
263
|
try {
|
|
252
|
-
const client = getClient();
|
|
264
|
+
const client = await getClient();
|
|
253
265
|
const result = await client.apiKey.delete({
|
|
254
266
|
keyId
|
|
255
267
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { type PayloadAuthClient } from '../../exports/client.js';
|
|
2
1
|
export type PasskeysManagementClientProps = {
|
|
3
2
|
/** Optional pre-configured auth client */
|
|
4
|
-
authClient?:
|
|
3
|
+
authClient?: any;
|
|
5
4
|
/** Page title. Default: 'Passkeys' */
|
|
6
5
|
title?: string;
|
|
7
6
|
};
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState, useEffect } from 'react';
|
|
3
|
+
import { useState, useEffect, useRef } from 'react';
|
|
4
4
|
import { Button, Banner } from '@payloadcms/ui';
|
|
5
5
|
import { PlusIcon } from '@payloadcms/ui/icons/Plus';
|
|
6
6
|
import { XIcon } from '@payloadcms/ui/icons/X';
|
|
7
|
-
import {
|
|
7
|
+
import { createAuthClient } from 'better-auth/react';
|
|
8
|
+
import { twoFactorClient } from 'better-auth/client/plugins';
|
|
8
9
|
/**
|
|
9
10
|
* Client component for passkey management.
|
|
10
11
|
* Lists, registers, and deletes passkeys.
|
|
@@ -17,7 +18,20 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
17
18
|
const [deleting, setDeleting] = useState(null);
|
|
18
19
|
const [showRegisterForm, setShowRegisterForm] = useState(false);
|
|
19
20
|
const [passkeyName, setPasskeyName] = useState('');
|
|
20
|
-
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
22
|
+
const clientRef = useRef(null);
|
|
23
|
+
const getClient = async ()=>{
|
|
24
|
+
if (providedClient) return providedClient;
|
|
25
|
+
if (clientRef.current) return clientRef.current;
|
|
26
|
+
const { passkeyClient } = await import('@better-auth/passkey/client');
|
|
27
|
+
clientRef.current = createAuthClient({
|
|
28
|
+
plugins: [
|
|
29
|
+
twoFactorClient(),
|
|
30
|
+
passkeyClient()
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
return clientRef.current;
|
|
34
|
+
};
|
|
21
35
|
useEffect(()=>{
|
|
22
36
|
fetchPasskeys();
|
|
23
37
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -26,7 +40,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
26
40
|
setLoading(true);
|
|
27
41
|
setError(null);
|
|
28
42
|
try {
|
|
29
|
-
const client = getClient();
|
|
43
|
+
const client = await getClient();
|
|
30
44
|
const result = await client.passkey.listUserPasskeys();
|
|
31
45
|
if (result.error) {
|
|
32
46
|
setError(result.error.message ?? 'Failed to load passkeys');
|
|
@@ -44,7 +58,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
44
58
|
setError(null);
|
|
45
59
|
setSuccess(null);
|
|
46
60
|
try {
|
|
47
|
-
const client = getClient();
|
|
61
|
+
const client = await getClient();
|
|
48
62
|
const result = await client.passkey.addPasskey({
|
|
49
63
|
name: passkeyName || undefined
|
|
50
64
|
});
|
|
@@ -76,7 +90,7 @@ import { createPayloadAuthClient } from '../../exports/client.js';
|
|
|
76
90
|
setError(null);
|
|
77
91
|
setSuccess(null);
|
|
78
92
|
try {
|
|
79
|
-
const client = getClient();
|
|
93
|
+
const client = await getClient();
|
|
80
94
|
const result = await client.passkey.deletePasskey({
|
|
81
95
|
id: passkeyId
|
|
82
96
|
});
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { type PayloadAuthClient } from '../../exports/client.js';
|
|
2
1
|
export type TwoFactorManagementClientProps = {
|
|
3
2
|
/** Optional pre-configured auth client */
|
|
4
|
-
authClient?:
|
|
3
|
+
authClient?: any;
|
|
5
4
|
/** Page title. Default: 'Two-Factor Authentication' */
|
|
6
5
|
title?: string;
|
|
7
6
|
/** Called after 2FA is enabled or disabled. Use to refresh form state. */
|