Skip to content

Commit 9052f38

Browse files
committed
fix(PayloadAdapter): Improve data transformation and forward additional fields
1 parent 3e48972 commit 9052f38

File tree

3 files changed

+73
-22
lines changed

3 files changed

+73
-22
lines changed

packages/payload-authjs/src/authjs/PayloadAdapter.ts

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import type {
22
Adapter,
3+
AdapterAccount,
34
AdapterSession,
45
AdapterUser,
56
VerificationToken as AdapterVerificationToken,
67
} from "next-auth/adapters";
78
import { type CollectionSlug, getPayload, type Payload, type SanitizedConfig } from "payload";
8-
import type { Session, User, VerificationToken } from "../payload/types";
9+
import type { Account, Session, User, VerificationToken } from "../payload/types";
10+
import { isDate } from "../utils/authjs";
911

1012
export interface PayloadAdapterOptions {
1113
/**
@@ -28,6 +30,7 @@ export interface PayloadAdapterOptions {
2830
}
2931
/**
3032
* Auth.js Database Adapter for Payload CMS
33+
*
3134
* @see https://authjs.dev/guides/creating-a-database-adapter
3235
*/
3336
export function PayloadAdapter({
@@ -162,7 +165,11 @@ export function PayloadAdapter({
162165
} satisfies Partial<User>,
163166
})) as User;
164167

165-
return account;
168+
const createdAccount = payloadUser.accounts?.find(
169+
a => a.provider === account.provider && a.providerAccountId === account.providerAccountId,
170+
);
171+
172+
return createdAccount ? toAdapterAccount(createdAccount) : account;
166173
},
167174
async unlinkAccount({ provider, providerAccountId }) {
168175
/* console.log(
@@ -198,7 +205,7 @@ export function PayloadAdapter({
198205
data: {
199206
accounts: payloadUser.accounts?.filter(
200207
account =>
201-
account.provider !== provider || account.providerAccountId !== providerAccountId,
208+
!(account.provider === provider && account.providerAccountId === providerAccountId),
202209
),
203210
},
204211
})) as User;
@@ -229,7 +236,11 @@ export function PayloadAdapter({
229236
},
230237
})) as User;
231238

232-
return session;
239+
const createdSession = payloadUser.sessions?.find(
240+
s => s.sessionToken === session.sessionToken,
241+
);
242+
243+
return createdSession ? toAdapterSession(payloadUser, createdSession) : session;
233244
},
234245
async getSessionAndUser(sessionToken) {
235246
/* console.log(`[PayloadAdapter] Getting session and user by session token '${sessionToken}'`); */
@@ -294,6 +305,7 @@ export function PayloadAdapter({
294305
const updatedSession = payloadUser.sessions?.find(
295306
s => s.sessionToken === session.sessionToken,
296307
);
308+
297309
return updatedSession ? toAdapterSession(payloadUser, updatedSession) : null;
298310
},
299311
async deleteSession(sessionToken) {
@@ -366,10 +378,14 @@ export function PayloadAdapter({
366378
})) as User;
367379
}
368380

369-
return {
370-
identifier: email,
371-
...token,
372-
};
381+
const createdToken = payloadUser.verificationTokens?.find(t => t.token === token.token);
382+
383+
return createdToken
384+
? toAdapterVerificationToken(payloadUser.email, createdToken)
385+
: {
386+
identifier: email,
387+
...token,
388+
};
373389
},
374390
async useVerificationToken({ identifier: email, token }) {
375391
/* console.log(`[PayloadAdapter] Using verification token for email '${email}'`, token); */
@@ -405,37 +421,62 @@ export function PayloadAdapter({
405421
},
406422
})) as User;
407423

408-
return verificationToken ? toAdapterVerificationToken(payloadUser, verificationToken) : null;
424+
return verificationToken
425+
? toAdapterVerificationToken(payloadUser.email, verificationToken)
426+
: null;
409427
},
410428
// #endregion
411429
};
412430
}
413431

414432
function toAdapterUser(user: User): AdapterUser {
415-
return {
416-
id: user.id,
417-
name: user.name,
418-
email: user.email,
419-
image: user.image,
420-
emailVerified: user.emailVerified ? new Date(user.emailVerified) : null,
421-
};
433+
return transformObject(user, ["accounts", "sessions", "verificationTokens"]);
434+
}
435+
436+
function toAdapterAccount(account: Account): AdapterAccount {
437+
return transformObject(account);
422438
}
423439

424440
function toAdapterSession(user: User, session: Session): AdapterSession {
425441
return {
442+
...transformObject<Session, Omit<AdapterSession, "userId">>(session),
426443
userId: user.id,
427-
sessionToken: session.sessionToken,
428-
expires: new Date(session.expires),
429444
};
430445
}
431446

432447
function toAdapterVerificationToken(
433-
user: User,
448+
email: string,
434449
token: VerificationToken,
435450
): AdapterVerificationToken {
436451
return {
437-
identifier: user.email,
438-
token: token.token,
439-
expires: new Date(token.expires),
452+
identifier: email,
453+
...transformObject<VerificationToken, Omit<AdapterVerificationToken, "identifier">>(token),
440454
};
441455
}
456+
457+
/**
458+
* Transform an object to an object that can be used by the adapter
459+
*
460+
* @param object Object to transform
461+
* @param exclude List of keys to remove from the object
462+
* @returns The transformed object
463+
*
464+
* @see https://authjs.dev/guides/creating-a-database-adapter#official-adapter-guidelines
465+
*/
466+
function transformObject<T extends Record<string, unknown>, AdapterObject extends object>(
467+
object: T,
468+
exclude?: (keyof T)[],
469+
): AdapterObject {
470+
const adapterObject: Record<string, unknown> = {};
471+
for (const [key, value] of Object.entries(object)) {
472+
if (exclude?.includes(key)) {
473+
continue;
474+
}
475+
if (isDate(value)) {
476+
adapterObject[key] = new Date(value);
477+
} else {
478+
adapterObject[key] = value;
479+
}
480+
}
481+
return adapterObject as AdapterObject;
482+
}

packages/payload-authjs/src/payload/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ export interface VerificationToken {
3232
id?: string | null;
3333
token: string;
3434
expires: string;
35+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
36+
[key: string]: any;
3537
}

packages/payload-authjs/src/utils/authjs.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
import type { NextAuthConfig } from "next-auth";
22

3+
// https://github.com/honeinc/is-iso-date/blob/8831e79b5b5ee615920dcb350a355ffc5cbf7aed/index.js#L5
4+
const isoDateRE =
5+
// eslint-disable-next-line regexp/no-unused-capturing-group
6+
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/;
7+
8+
export const isDate = (val: any): val is ConstructorParameters<typeof Date>[0] =>
9+
!!(val && isoDateRE.test(val) && !isNaN(Date.parse(val)));
10+
311
/**
412
* Check if an email provider is available in the authjs config
513
*/

0 commit comments

Comments
 (0)