@delmaredigital/payload-better-auth 0.5.2 → 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');
@@ -2,7 +2,6 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useState, useEffect, useMemo, useRef } from 'react';
4
4
  import { createAuthClient } from 'better-auth/react';
5
- import { apiKeyClient } from '@better-auth/api-key/client';
6
5
  /**
7
6
  * Group scopes by collection for the UI.
8
7
  * Scopes like "posts:read", "posts:write" get grouped under "Posts"
@@ -90,11 +89,19 @@ import { apiKeyClient } from '@better-auth/api-key/client';
90
89
  }, [
91
90
  scopeGroups
92
91
  ]);
93
- const getClient = ()=>providedClient ?? createAuthClient({
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({
94
99
  plugins: [
95
100
  apiKeyClient()
96
101
  ]
97
102
  });
103
+ return clientRef.current;
104
+ };
98
105
  // Toggle a scope selection
99
106
  function toggleScope(scopeId) {
100
107
  setSelectedScopes((prev)=>prev.includes(scopeId) ? prev.filter((s)=>s !== scopeId) : [
@@ -198,7 +205,7 @@ import { apiKeyClient } from '@better-auth/api-key/client';
198
205
  setLoading(true);
199
206
  setError(null);
200
207
  try {
201
- const client = getClient();
208
+ const client = await getClient();
202
209
  const result = await client.apiKey.list();
203
210
  if (result.error) {
204
211
  setError(result.error.message ?? 'Failed to load API keys');
@@ -218,7 +225,7 @@ import { apiKeyClient } from '@better-auth/api-key/client';
218
225
  setError(null);
219
226
  setNewlyCreatedKey(null);
220
227
  try {
221
- const client = getClient();
228
+ const client = await getClient();
222
229
  // Send scopes to server - server will convert to permissions
223
230
  const createOptions = {
224
231
  name: newKeyName
@@ -254,7 +261,7 @@ import { apiKeyClient } from '@better-auth/api-key/client';
254
261
  setDeleting(keyId);
255
262
  setError(null);
256
263
  try {
257
- const client = getClient();
264
+ const client = await getClient();
258
265
  const result = await client.apiKey.delete({
259
266
  keyId
260
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. */