@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 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?: PayloadAuthClient;
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 { createPayloadAuthClient } from '../exports/client.js';
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
- const getClient = ()=>providedClient ?? createPayloadAuthClient();
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?: PayloadAuthClient;
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 { createPayloadAuthClient } from '../exports/client.js';
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 = providedClient ?? createPayloadAuthClient();
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?: PayloadAuthClient;
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 { createPayloadAuthClient } from '../exports/client.js';
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 = providedClient ?? createPayloadAuthClient();
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?: PayloadAuthClient;
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 { createPayloadAuthClient } from '../../exports/client.js';
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
- const getClient = ()=>providedClient ?? createPayloadAuthClient();
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?: PayloadAuthClient;
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 { createPayloadAuthClient } from '../../exports/client.js';
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
- const getClient = ()=>providedClient ?? createPayloadAuthClient();
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?: PayloadAuthClient;
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. */