@delmaredigital/payload-better-auth 0.4.1 → 0.4.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 +116 -5
- package/dist/adapter/collections.js +9 -1
- package/dist/adapter/index.d.ts +19 -0
- package/dist/adapter/index.js +96 -32
- package/dist/components/LogoutButton.d.ts +3 -0
- package/dist/components/LogoutButton.js +20 -8
- package/dist/components/management/PasskeysManagementClient.js +100 -197
- package/dist/components/management/SecurityNavLinks.d.ts +3 -6
- package/dist/components/management/SecurityNavLinks.js +12 -30
- package/dist/components/management/TwoFactorManagementClient.d.ts +3 -1
- package/dist/components/management/TwoFactorManagementClient.js +115 -248
- package/dist/components/management/fields/PasskeysField.d.ts +13 -0
- package/dist/components/management/fields/PasskeysField.js +32 -0
- package/dist/components/management/fields/TwoFactorField.d.ts +19 -0
- package/dist/components/management/fields/TwoFactorField.js +48 -0
- package/dist/exports/components.d.ts +2 -0
- package/dist/exports/components.js +3 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/plugin/index.js +47 -27
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -20,6 +20,7 @@ For additional documentation and references, visit: [https://deepwiki.com/delmar
|
|
|
20
20
|
|
|
21
21
|
- [Installation](#installation)
|
|
22
22
|
- [Quick Start](#quick-start)
|
|
23
|
+
- [MongoDB Setup](#mongodb-setup)
|
|
23
24
|
- [API Reference](#api-reference)
|
|
24
25
|
- [Customization](#customization)
|
|
25
26
|
- [Access Control Helpers](#access-control-helpers)
|
|
@@ -166,7 +167,9 @@ export const Users: CollectionConfig = {
|
|
|
166
167
|
> [better-auth] Auto-adding fields to 'users': ['twoFactorEnabled']
|
|
167
168
|
> ```
|
|
168
169
|
|
|
169
|
-
### Step 3: Configure Payload
|
|
170
|
+
### Step 3: Configure Payload (Postgres)
|
|
171
|
+
|
|
172
|
+
> For MongoDB, see [MongoDB Setup](#mongodb-setup).
|
|
170
173
|
|
|
171
174
|
```ts
|
|
172
175
|
// src/payload.config.ts
|
|
@@ -292,6 +295,111 @@ export default async function Dashboard() {
|
|
|
292
295
|
|
|
293
296
|
---
|
|
294
297
|
|
|
298
|
+
## MongoDB Setup
|
|
299
|
+
|
|
300
|
+
The adapter auto-detects MongoDB and configures itself accordingly. No special adapter configuration is needed.
|
|
301
|
+
|
|
302
|
+
### Key Differences from Postgres
|
|
303
|
+
|
|
304
|
+
| | Postgres (default) | MongoDB |
|
|
305
|
+
|---|---|---|
|
|
306
|
+
| **ID type** | `'number'` (SERIAL) | `'text'` (ObjectId strings) |
|
|
307
|
+
| **`generateId`** | Set to `'serial'` | Do not set (MongoDB generates ObjectIds) |
|
|
308
|
+
| **`idType`** | `'number'` (default) | Auto-detected as `'text'` |
|
|
309
|
+
| **`betterAuthStrategy`** | `idType: 'number'` (default) | `idType: 'text'` |
|
|
310
|
+
| **`createSessionHelpers`** | `idType: 'number'` | `idType: 'text'` (or omit) |
|
|
311
|
+
|
|
312
|
+
### Quick Start (MongoDB)
|
|
313
|
+
|
|
314
|
+
```ts
|
|
315
|
+
// src/payload.config.ts
|
|
316
|
+
import { buildConfig } from 'payload'
|
|
317
|
+
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
|
318
|
+
import { betterAuth } from 'better-auth'
|
|
319
|
+
import {
|
|
320
|
+
betterAuthCollections,
|
|
321
|
+
createBetterAuthPlugin,
|
|
322
|
+
payloadAdapter,
|
|
323
|
+
} from '@delmaredigital/payload-better-auth'
|
|
324
|
+
import { betterAuthOptions } from './lib/auth/config'
|
|
325
|
+
import { Users } from './collections/Users'
|
|
326
|
+
|
|
327
|
+
export default buildConfig({
|
|
328
|
+
collections: [Users],
|
|
329
|
+
plugins: [
|
|
330
|
+
betterAuthCollections({
|
|
331
|
+
betterAuthOptions,
|
|
332
|
+
skipCollections: ['user'],
|
|
333
|
+
}),
|
|
334
|
+
createBetterAuthPlugin({
|
|
335
|
+
createAuth: (payload) =>
|
|
336
|
+
betterAuth({
|
|
337
|
+
...betterAuthOptions,
|
|
338
|
+
database: payloadAdapter({ payloadClient: payload }),
|
|
339
|
+
// Do NOT set advanced.database.generateId — MongoDB generates ObjectIds
|
|
340
|
+
secret: process.env.BETTER_AUTH_SECRET,
|
|
341
|
+
trustedOrigins: ['http://localhost:3000'],
|
|
342
|
+
}),
|
|
343
|
+
}),
|
|
344
|
+
],
|
|
345
|
+
db: mongooseAdapter({
|
|
346
|
+
url: process.env.DATABASE_URI!,
|
|
347
|
+
}),
|
|
348
|
+
})
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
**Users collection:**
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
import { betterAuthStrategy } from '@delmaredigital/payload-better-auth'
|
|
355
|
+
|
|
356
|
+
export const Users: CollectionConfig = {
|
|
357
|
+
slug: 'users',
|
|
358
|
+
auth: {
|
|
359
|
+
disableLocalStrategy: true,
|
|
360
|
+
strategies: [betterAuthStrategy({ idType: 'text' })],
|
|
361
|
+
},
|
|
362
|
+
// ... fields and access control same as Postgres setup
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Session helpers:**
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
import { createSessionHelpers } from '@delmaredigital/payload-better-auth'
|
|
370
|
+
import type { User } from '@/payload-types'
|
|
371
|
+
|
|
372
|
+
export const { getServerSession, getServerUser } = createSessionHelpers<User>({
|
|
373
|
+
idType: 'text',
|
|
374
|
+
})
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Migrating from Postgres to MongoDB
|
|
378
|
+
|
|
379
|
+
1. **Remove `generateId: 'serial'`** from your Better Auth config (the `advanced.database` block)
|
|
380
|
+
2. **Change `betterAuthStrategy()`** to use `idType: 'text'`:
|
|
381
|
+
```ts
|
|
382
|
+
strategies: [betterAuthStrategy({ idType: 'text' })]
|
|
383
|
+
```
|
|
384
|
+
3. **Update `createSessionHelpers`** (if using) to `idType: 'text'`:
|
|
385
|
+
```ts
|
|
386
|
+
createSessionHelpers<User>({ idType: 'text' })
|
|
387
|
+
```
|
|
388
|
+
4. **Remove `idType: 'number'`** from `adapterConfig` if you had it set explicitly — the adapter auto-detects `'text'` for MongoDB
|
|
389
|
+
5. **Switch the Payload database adapter**:
|
|
390
|
+
```ts
|
|
391
|
+
// Before
|
|
392
|
+
import { postgresAdapter } from '@payloadcms/db-postgres'
|
|
393
|
+
db: postgresAdapter({ pool: { connectionString: process.env.DATABASE_URL } })
|
|
394
|
+
|
|
395
|
+
// After
|
|
396
|
+
import { mongooseAdapter } from '@payloadcms/db-mongodb'
|
|
397
|
+
db: mongooseAdapter({ url: process.env.DATABASE_URI! })
|
|
398
|
+
```
|
|
399
|
+
6. **Remove `idFieldsAllowlist`/`idFieldsBlocklist`** from `adapterConfig` if set — these are only relevant for serial ID conversion
|
|
400
|
+
|
|
401
|
+
---
|
|
402
|
+
|
|
295
403
|
## API Reference
|
|
296
404
|
|
|
297
405
|
### `payloadAdapter(config)`
|
|
@@ -312,13 +420,16 @@ payloadAdapter({
|
|
|
312
420
|
|--------|------|-------------|
|
|
313
421
|
| `payloadClient` | `BasePayload \| () => Promise<BasePayload>` | Payload instance or factory function |
|
|
314
422
|
| `adapterConfig.enableDebugLogs` | `boolean` | Enable debug logging (default: `false`) |
|
|
315
|
-
| `adapterConfig.
|
|
423
|
+
| `adapterConfig.dbType` | `'postgres' \| 'mongodb' \| 'sqlite'` | Database type (auto-detected from Payload's adapter) |
|
|
424
|
+
| `adapterConfig.idType` | `'number' \| 'text'` | ID type (auto-detected: `'number'` for Postgres/SQLite, `'text'` for MongoDB) |
|
|
316
425
|
| `adapterConfig.idFieldsAllowlist` | `string[]` | Additional fields to convert to numeric IDs (default: `[]`) |
|
|
317
426
|
| `adapterConfig.idFieldsBlocklist` | `string[]` | Fields to exclude from numeric ID conversion (default: `[]`) |
|
|
318
427
|
|
|
319
|
-
**ID Type:**
|
|
320
|
-
-
|
|
321
|
-
-
|
|
428
|
+
**Database & ID Type:**
|
|
429
|
+
- **Auto-detected**: The adapter reads `payload.db.name` to determine the database type, then sets `idType` accordingly
|
|
430
|
+
- **Postgres/SQLite**: `idType` defaults to `'number'` (SERIAL IDs)
|
|
431
|
+
- **MongoDB**: `idType` is always `'text'` (ObjectId strings)
|
|
432
|
+
- Set `dbType` explicitly only if auto-detection doesn't work for your adapter
|
|
322
433
|
|
|
323
434
|
**Note:** When using number IDs (default), you can optionally set `generateId: 'serial'` in Better Auth to be explicit:
|
|
324
435
|
```typescript
|
|
@@ -303,11 +303,19 @@ function generateCollection(modelKey, table, usePlural, adminGroup, customAccess
|
|
|
303
303
|
});
|
|
304
304
|
} else {
|
|
305
305
|
const saveToJWT = configureSaveToJWT ? getSaveToJWT(modelKey, payloadFieldName) : undefined;
|
|
306
|
+
// Fields managed exclusively by Better Auth should be read-only in the admin UI
|
|
307
|
+
const readOnlyFields = [
|
|
308
|
+
'twoFactorEnabled'
|
|
309
|
+
];
|
|
310
|
+
const isReadOnly = readOnlyFields.includes(payloadFieldName);
|
|
306
311
|
const field = {
|
|
307
312
|
name: payloadFieldName,
|
|
308
313
|
type: fieldType,
|
|
309
314
|
admin: {
|
|
310
|
-
description: `Auto-added by Better Auth (${fieldKey})
|
|
315
|
+
description: `Auto-added by Better Auth (${fieldKey})`,
|
|
316
|
+
...isReadOnly && {
|
|
317
|
+
readOnly: true
|
|
318
|
+
}
|
|
311
319
|
},
|
|
312
320
|
...saveToJWT !== undefined && {
|
|
313
321
|
saveToJWT
|
package/dist/adapter/index.d.ts
CHANGED
|
@@ -8,6 +8,20 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { Adapter, BetterAuthOptions } from 'better-auth';
|
|
10
10
|
import type { BasePayload } from 'payload';
|
|
11
|
+
/**
|
|
12
|
+
* Database types supported by Payload CMS.
|
|
13
|
+
*/
|
|
14
|
+
export type DbType = 'postgres' | 'mongodb' | 'sqlite';
|
|
15
|
+
/**
|
|
16
|
+
* Detect the database type from the Payload instance.
|
|
17
|
+
*/
|
|
18
|
+
export declare function detectDbType(payload: BasePayload): DbType;
|
|
19
|
+
/**
|
|
20
|
+
* Determine ID type based on database type and Better Auth config.
|
|
21
|
+
* MongoDB always uses text IDs (ObjectId strings).
|
|
22
|
+
* Postgres defaults to 'number' (SERIAL) unless generateId indicates otherwise.
|
|
23
|
+
*/
|
|
24
|
+
export declare function resolveIdType(dbType: DbType, options: BetterAuthOptions, explicitIdType?: 'number' | 'text'): 'number' | 'text';
|
|
11
25
|
export type PayloadAdapterConfig = {
|
|
12
26
|
/**
|
|
13
27
|
* The Payload instance or a function that returns it.
|
|
@@ -22,6 +36,11 @@ export type PayloadAdapterConfig = {
|
|
|
22
36
|
* Enable debug logging for troubleshooting
|
|
23
37
|
*/
|
|
24
38
|
enableDebugLogs?: boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Database type. Auto-detected from Payload's database adapter if not set.
|
|
41
|
+
* Set explicitly if auto-detection doesn't work for your adapter.
|
|
42
|
+
*/
|
|
43
|
+
dbType?: DbType;
|
|
25
44
|
/**
|
|
26
45
|
* ID type used by Payload.
|
|
27
46
|
* If not specified, auto-detects from Better Auth's generateId setting.
|
package/dist/adapter/index.js
CHANGED
|
@@ -7,15 +7,26 @@
|
|
|
7
7
|
* @packageDocumentation
|
|
8
8
|
*/ import { createAdapterFactory } from 'better-auth/adapters';
|
|
9
9
|
/**
|
|
10
|
-
* Detect
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
* Detect the database type from the Payload instance.
|
|
11
|
+
*/ export function detectDbType(payload) {
|
|
12
|
+
const dbName = payload.db?.name;
|
|
13
|
+
if (typeof dbName === 'string') {
|
|
14
|
+
if (dbName.includes('mongo') || dbName.includes('mongoose')) return 'mongodb';
|
|
15
|
+
if (dbName.includes('sqlite')) return 'sqlite';
|
|
16
|
+
}
|
|
17
|
+
return 'postgres';
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Determine ID type based on database type and Better Auth config.
|
|
21
|
+
* MongoDB always uses text IDs (ObjectId strings).
|
|
22
|
+
* Postgres defaults to 'number' (SERIAL) unless generateId indicates otherwise.
|
|
23
|
+
*/ export function resolveIdType(dbType, options, explicitIdType) {
|
|
24
|
+
if (dbType === 'mongodb') return 'text';
|
|
25
|
+
if (explicitIdType) return explicitIdType;
|
|
13
26
|
const generateId = options.advanced?.database?.generateId;
|
|
14
|
-
// If explicitly set to something other than 'serial', use text (UUID)
|
|
15
27
|
if (generateId !== undefined && generateId !== 'serial') {
|
|
16
28
|
return 'text';
|
|
17
29
|
}
|
|
18
|
-
// Default to number (SERIAL) - Payload's default
|
|
19
30
|
return 'number';
|
|
20
31
|
}
|
|
21
32
|
/**
|
|
@@ -59,7 +70,7 @@
|
|
|
59
70
|
async function resolvePayloadClient() {
|
|
60
71
|
return typeof payloadClient === 'function' ? await payloadClient() : payloadClient;
|
|
61
72
|
}
|
|
62
|
-
function convertOperator(operator, value) {
|
|
73
|
+
function convertOperator(operator, value, dbType) {
|
|
63
74
|
switch(operator){
|
|
64
75
|
case 'eq':
|
|
65
76
|
return {
|
|
@@ -89,15 +100,25 @@
|
|
|
89
100
|
return {
|
|
90
101
|
in: value
|
|
91
102
|
};
|
|
103
|
+
case 'not_in':
|
|
104
|
+
return {
|
|
105
|
+
not_in: value
|
|
106
|
+
};
|
|
92
107
|
case 'contains':
|
|
93
108
|
return {
|
|
94
109
|
contains: value
|
|
95
110
|
};
|
|
96
111
|
case 'starts_with':
|
|
112
|
+
if (dbType === 'mongodb') return {
|
|
113
|
+
contains: value
|
|
114
|
+
};
|
|
97
115
|
return {
|
|
98
116
|
like: `${value}%`
|
|
99
117
|
};
|
|
100
118
|
case 'ends_with':
|
|
119
|
+
if (dbType === 'mongodb') return {
|
|
120
|
+
contains: value
|
|
121
|
+
};
|
|
101
122
|
return {
|
|
102
123
|
like: `%${value}`
|
|
103
124
|
};
|
|
@@ -122,9 +143,11 @@
|
|
|
122
143
|
}
|
|
123
144
|
// Return the adapter factory function
|
|
124
145
|
return (options)=>{
|
|
125
|
-
// Determine ID type
|
|
126
|
-
//
|
|
127
|
-
|
|
146
|
+
// Determine ID type based on database type
|
|
147
|
+
// If payloadClient is already resolved, detect dbType immediately
|
|
148
|
+
// Otherwise default to 'postgres' (will be updated on first operation)
|
|
149
|
+
const effectiveDbType = adapterConfig.dbType ?? (typeof payloadClient !== 'function' ? detectDbType(payloadClient) : 'postgres');
|
|
150
|
+
const idType = resolveIdType(effectiveDbType, options, adapterConfig.idType);
|
|
128
151
|
const generateId = options.advanced?.database?.generateId;
|
|
129
152
|
// Warn if using number IDs but Better Auth is explicitly configured to generate its own IDs
|
|
130
153
|
// This would cause Better Auth to generate UUIDs which won't work with SERIAL columns
|
|
@@ -153,10 +176,10 @@
|
|
|
153
176
|
// Payload collections are plural by default (users, sessions, etc.)
|
|
154
177
|
// Users can customize via BetterAuthOptions: user: { modelName: 'custom_users' }
|
|
155
178
|
usePlural: true,
|
|
156
|
-
//
|
|
157
|
-
disableIdGeneration:
|
|
158
|
-
//
|
|
159
|
-
supportsNumericIds:
|
|
179
|
+
// Payload always generates IDs (SERIAL for postgres/sqlite, ObjectId for mongodb)
|
|
180
|
+
disableIdGeneration: true,
|
|
181
|
+
// MongoDB uses ObjectId strings, not numeric IDs
|
|
182
|
+
supportsNumericIds: effectiveDbType !== 'mongodb',
|
|
160
183
|
supportsDates: true,
|
|
161
184
|
supportsBooleans: true,
|
|
162
185
|
supportsJSON: true,
|
|
@@ -171,11 +194,18 @@
|
|
|
171
194
|
// so we'll resolve it lazily on first operation
|
|
172
195
|
let resolvedPayload = null;
|
|
173
196
|
let resolvePromise = null;
|
|
197
|
+
let resolvedDbType = adapterConfig.dbType ?? null;
|
|
174
198
|
const getPayload = async ()=>{
|
|
175
199
|
if (resolvedPayload) return resolvedPayload;
|
|
176
200
|
if (!resolvePromise) {
|
|
177
201
|
resolvePromise = resolvePayloadClient().then((p)=>{
|
|
178
202
|
resolvedPayload = p;
|
|
203
|
+
if (!resolvedDbType) {
|
|
204
|
+
resolvedDbType = detectDbType(p);
|
|
205
|
+
if (enableDebugLogs) {
|
|
206
|
+
console.log('[payload-adapter] Detected database type:', resolvedDbType);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
179
209
|
return p;
|
|
180
210
|
});
|
|
181
211
|
}
|
|
@@ -295,19 +325,6 @@
|
|
|
295
325
|
}
|
|
296
326
|
}
|
|
297
327
|
}
|
|
298
|
-
// Convert date strings to Date objects
|
|
299
|
-
// Better Auth expects Date objects for date field comparisons
|
|
300
|
-
for (const [key, value] of Object.entries(transformed)){
|
|
301
|
-
if (typeof value !== 'string') continue;
|
|
302
|
-
// Check if schema defines this field as a date type
|
|
303
|
-
const fieldDef = modelSchema.fields[key];
|
|
304
|
-
if (fieldDef?.type === 'date') {
|
|
305
|
-
const dateValue = new Date(value);
|
|
306
|
-
if (!isNaN(dateValue.getTime())) {
|
|
307
|
-
transformed[key] = dateValue;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
328
|
// Convert semantic ID fields to numbers when using serial IDs
|
|
312
329
|
// Heuristic: fields ending in 'Id' or '_id' containing numeric strings
|
|
313
330
|
// Modified by allowlist (add) and blocklist (exclude)
|
|
@@ -337,7 +354,7 @@
|
|
|
337
354
|
if (where.length === 1) {
|
|
338
355
|
const w = where[0];
|
|
339
356
|
return {
|
|
340
|
-
[getPayloadFieldName(model, w.field)]: convertOperator(w.operator, w.value)
|
|
357
|
+
[getPayloadFieldName(model, w.field)]: convertOperator(w.operator, w.value, resolvedDbType ?? 'postgres')
|
|
341
358
|
};
|
|
342
359
|
}
|
|
343
360
|
const andConditions = where.filter((w)=>w.connector !== 'OR');
|
|
@@ -345,12 +362,12 @@
|
|
|
345
362
|
const result = {};
|
|
346
363
|
if (andConditions.length > 0) {
|
|
347
364
|
result.and = andConditions.map((w)=>({
|
|
348
|
-
[getPayloadFieldName(model, w.field)]: convertOperator(w.operator, w.value)
|
|
365
|
+
[getPayloadFieldName(model, w.field)]: convertOperator(w.operator, w.value, resolvedDbType ?? 'postgres')
|
|
349
366
|
}));
|
|
350
367
|
}
|
|
351
368
|
if (orConditions.length > 0) {
|
|
352
369
|
result.or = orConditions.map((w)=>({
|
|
353
|
-
[getPayloadFieldName(model, w.field)]: convertOperator(w.operator, w.value)
|
|
370
|
+
[getPayloadFieldName(model, w.field)]: convertOperator(w.operator, w.value, resolvedDbType ?? 'postgres')
|
|
354
371
|
}));
|
|
355
372
|
}
|
|
356
373
|
return result;
|
|
@@ -389,10 +406,19 @@
|
|
|
389
406
|
// Transform back and merge with input data for Better Auth
|
|
390
407
|
// Database result takes precedence (handles hooks that modify data like firstUserAdmin)
|
|
391
408
|
const transformed = transformDataFromPayload(model, result);
|
|
392
|
-
|
|
409
|
+
const merged = {
|
|
393
410
|
...data,
|
|
394
411
|
...transformed
|
|
395
412
|
};
|
|
413
|
+
if (enableDebugLogs) {
|
|
414
|
+
debugLog('create result', {
|
|
415
|
+
collection,
|
|
416
|
+
resultId: result.id,
|
|
417
|
+
transformedKeys: Object.keys(transformed),
|
|
418
|
+
mergedKeys: Object.keys(merged)
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
return merged;
|
|
396
422
|
} catch (error) {
|
|
397
423
|
console.error('[payload-adapter] create failed:', {
|
|
398
424
|
collection,
|
|
@@ -424,15 +450,39 @@
|
|
|
424
450
|
depth: join ? 1 : 0,
|
|
425
451
|
overrideAccess: true
|
|
426
452
|
});
|
|
427
|
-
|
|
453
|
+
const transformed = transformDataFromPayload(model, result);
|
|
454
|
+
if (enableDebugLogs) {
|
|
455
|
+
debugLog('findOne result (byID)', {
|
|
456
|
+
collection,
|
|
457
|
+
id: convertId(id),
|
|
458
|
+
found: true,
|
|
459
|
+
transformedKeys: Object.keys(transformed)
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
return transformed;
|
|
428
463
|
} catch (error) {
|
|
429
464
|
if (error instanceof Error && 'status' in error && error.status === 404) {
|
|
465
|
+
if (enableDebugLogs) {
|
|
466
|
+
debugLog('findOne result (byID)', {
|
|
467
|
+
collection,
|
|
468
|
+
id: convertId(id),
|
|
469
|
+
found: false
|
|
470
|
+
});
|
|
471
|
+
}
|
|
430
472
|
return null;
|
|
431
473
|
}
|
|
432
474
|
throw error;
|
|
433
475
|
}
|
|
434
476
|
}
|
|
435
477
|
const payloadWhere = convertWhereToPayload(model, where);
|
|
478
|
+
if (enableDebugLogs) {
|
|
479
|
+
debugLog('findOne query', {
|
|
480
|
+
collection,
|
|
481
|
+
payloadWhere: JSON.stringify(payloadWhere),
|
|
482
|
+
resolvedDbType,
|
|
483
|
+
idType
|
|
484
|
+
});
|
|
485
|
+
}
|
|
436
486
|
const result = await payload.find({
|
|
437
487
|
collection,
|
|
438
488
|
where: payloadWhere,
|
|
@@ -440,8 +490,22 @@
|
|
|
440
490
|
depth: join ? 1 : 0,
|
|
441
491
|
overrideAccess: true
|
|
442
492
|
});
|
|
493
|
+
if (enableDebugLogs) {
|
|
494
|
+
debugLog('findOne result', {
|
|
495
|
+
collection,
|
|
496
|
+
totalDocs: result.totalDocs,
|
|
497
|
+
found: result.docs.length > 0
|
|
498
|
+
});
|
|
499
|
+
}
|
|
443
500
|
if (!result.docs[0]) return null;
|
|
444
|
-
|
|
501
|
+
const transformed = transformDataFromPayload(model, result.docs[0]);
|
|
502
|
+
if (enableDebugLogs) {
|
|
503
|
+
debugLog('findOne transformed', {
|
|
504
|
+
collection,
|
|
505
|
+
transformedKeys: Object.keys(transformed)
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
return transformed;
|
|
445
509
|
} catch (error) {
|
|
446
510
|
console.error('[payload-adapter] findOne failed:', {
|
|
447
511
|
model,
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Logout button component styled to match Payload's admin nav.
|
|
3
3
|
* Uses Payload's CSS classes and variables for native theme integration.
|
|
4
|
+
*
|
|
5
|
+
* Clears both Better Auth session and Payload's JWT cookie to ensure
|
|
6
|
+
* clean state when switching between users.
|
|
4
7
|
*/
|
|
5
8
|
export declare function LogoutButton(): import("react").JSX.Element;
|
|
6
9
|
export default LogoutButton;
|
|
@@ -5,6 +5,9 @@ import { useRouter } from 'next/navigation.js';
|
|
|
5
5
|
/**
|
|
6
6
|
* Logout button component styled to match Payload's admin nav.
|
|
7
7
|
* Uses Payload's CSS classes and variables for native theme integration.
|
|
8
|
+
*
|
|
9
|
+
* Clears both Better Auth session and Payload's JWT cookie to ensure
|
|
10
|
+
* clean state when switching between users.
|
|
8
11
|
*/ export function LogoutButton() {
|
|
9
12
|
const router = useRouter();
|
|
10
13
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -12,14 +15,23 @@ import { useRouter } from 'next/navigation.js';
|
|
|
12
15
|
if (isLoading) return;
|
|
13
16
|
setIsLoading(true);
|
|
14
17
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
// Clear both sessions simultaneously while cookies are still valid.
|
|
19
|
+
// - Better Auth: clears BA session cookie
|
|
20
|
+
// - Payload: clears JWT cookie (payload-token) so useAuth() resets
|
|
21
|
+
await Promise.allSettled([
|
|
22
|
+
fetch('/api/auth/sign-out', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
credentials: 'include',
|
|
25
|
+
headers: {
|
|
26
|
+
'Content-Type': 'application/json'
|
|
27
|
+
},
|
|
28
|
+
body: JSON.stringify({})
|
|
29
|
+
}),
|
|
30
|
+
fetch('/api/users/logout', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
credentials: 'include'
|
|
33
|
+
})
|
|
34
|
+
]);
|
|
23
35
|
router.push('/admin/login');
|
|
24
36
|
} catch (error) {
|
|
25
37
|
console.error('[better-auth] Logout error:', error);
|