next-auth
next-auth copied to clipboard
drizzle-adapter with custom user table?
Currently when a user is created, adapter-drizzle uses default values defaultSqliteTableFn
for users, accounts, sessions, verificationTokens
tables which means if we use a custom oauth provider and extend what the profile function is returning to create a user, none of the data will be added to the users table.
My question is: How do we send data to DrizzleAdapter so it creates a custom user?
It would be nice to see the documentation updated with this info.
export function SQLiteDrizzleAdapter(
client: InstanceType<typeof BaseSQLiteDatabase>,
tableFn = defaultSqliteTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
async createUser(data) {
return await client
.insert(users)
.values({ ...data, id: crypto.randomUUID() })
.returning()
.get()
},
Update I finally found a way to override the default tables now.
export const tableFn = (table: string) => {
switch (table) {
case 'user':
return users;
case 'account':
return accounts;
case 'session':
return sessions;
case 'verificationToken':
return verificationTokens;
default:
throw new Error(`Table ${table} not found`);
}
};
....
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
adapter: DrizzleAdapter(locals?.D1, tableFn),
This was the only way I managed the DrizzleAdapter to override.
Most solutions I've seen used export const createTable = sqliteTableCreator((name) => name);
but that one didn't work for me. Any ideas why?
The issue seems that the adapter is querying from the table it defines (sqlite as example as i'm using that database, but it's the same for pg
https://github.com/nextauthjs/next-auth/blob/9c0de61a55288e7c4f6bdaebc160d033ac39d8f1/packages/adapter-drizzle/src/lib/sqlite.ts#L84
The issue seems that the adapter is querying from the table it defines (sqlite as example as i'm using that database, but it's the same for pg
https://github.com/nextauthjs/next-auth/blob/9c0de61a55288e7c4f6bdaebc160d033ac39d8f1/packages/adapter-drizzle/src/lib/sqlite.ts#L84
Ran into this today. Seems to me you just have to copy the adapter and modify it yourself. Unless I am missing something
The issue seems that the adapter is querying from the table it defines (sqlite as example as i'm using that database, but it's the same for pg https://github.com/nextauthjs/next-auth/blob/9c0de61a55288e7c4f6bdaebc160d033ac39d8f1/packages/adapter-drizzle/src/lib/sqlite.ts#L84
Ran into this today. Seems to me you just have to copy the adapter and modify it yourself. Unless I am missing something
That worked for me as well, or using the solution I updated.
@socketopp Are you sure everything works?
https://github.com/nextauthjs/next-auth/blob/d85269d1169d068448b831d2ee9517e4673c78b8/packages/adapter-drizzle/src/lib/pg.ts#L161
This line specifically assumes that the table name is user
and not users
@socketopp Are you sure everything works?
https://github.com/nextauthjs/next-auth/blob/d85269d1169d068448b831d2ee9517e4673c78b8/packages/adapter-drizzle/src/lib/pg.ts#L161
This line specifically assumes that the table name is
user
and notusers
I am not sure I follow. The name of the table is 'user' and the variable is named users.
I ran into this today as well when attempting to attach a role to the user object. The problem is, that the drizzleAdapter uses default table definitions when accepting data to the tables to be inserted.
Example PG Adapter:
export function pgDrizzleAdapter(
client: InstanceType<typeof PgDatabase>,
tableFn = defaultPgTableFn
): Adapter {
const { users, accounts, sessions, verificationTokens } =
createTables(tableFn)
return {
async createUser(data) {
return await client
.insert(users)
.values({ ...data, id: crypto.randomUUID() })
.returning()
.then((res) => res[0] ?? null)
},
createTables()
export function createTables(pgTable: PgTableFn) {
const users = pgTable("user", {
id: text("id").notNull().primaryKey(),
name: text("name"),
email: text("email").notNull(),
emailVerified: timestamp("emailVerified", { mode: "date" }),
image: text("image"),
})
It looks like the function can take an optional pgTable function, but I fail to see how this can be used to motify the default table schema included.
My Solution
function customAdapter(): Adapter {
const adapter = DrizzleAdapter(db);
// Overwrite createUser method on adapter
adapter.createUser = async (data): Promise<AdapterUser> => {
const dataEntered = await db
.insert(users)
.values({ ...data, id: crypto.randomUUID() })
.returning()
.then((res) => res[0] ?? null);
if (!dataEntered) {
throw new Error("User Creation Failed");
}
return dataEntered;
};
return {
...adapter,
};
}
I simply overwrite the existing createUser method on the adapter, with the exactly the same function, but I pass my own users schema into the db.insert(). I can then pass my own adapter into the next-auth config to have functional additional roles.
It might be nice if we could pass an optional schema to the adapter to use instead, similair to the drizzle-kit config.
@JavanFriedel If you do that then you can't do module augmentation on session.
Adapter from next-auth/adapters is moved to @auth/core/adapters. If you are creating a custom adapter, use @auth/core instead of next-auth.
I ran into that issue while implementing your solution, had to create the adpater for all the methods, else it fails.
@adriangalilea I believe I am importing directly from @auth for the types as well as the drizzle adapter directly.
import { DrizzleAdapter } from "@auth/drizzle-adapter";
import type { Adapter, AdapterUser } from "@auth/core/adapters";
Essentially the createUser is the only method I needed to modify for my use case, and the rest should be in-tact as I am spreading the methods available on the original adapter, and returning it.
If I needed to modify any of the other methods like update etc, I would have to modify those as well. At some point it would make more sense to just write my own adapter from scratch as outlined in the auth.js docs.
@JavanFriedel Yeah, but what I'm saying is that if you both:
- use a custom adapter
- do module augmentation
You'll get problems like session not having the properties you want it to. You can take a look at this gist
If I remove the other methods or spread them, my printed session is:
{
"user": {
"id": "...",
"name": "Adrian Galilea",
"email": "[email protected]",
"emailVerified": null,
"image": null
},
"sessionToken": "...",
"userId": "...",
"expires": "2024-04-17T21:00:15.584Z"
}
However if I copy paste them all, from the basic sqlite adapter, it works as expected.
{
"user": {
"id": "",
"name": "Adrian Galilea",
"email": "[email protected]",
"emailVerified": null,
"image": null,
"gh_username": "adriangalilea",
"gh_id": "",
"gh_image": "https://avatars.githubusercontent.com/u/90320947?v=4",
"country_code": "XX",
"avatar": null,
"created_at": "2024-03-18 21:02:08",
"updated_at": "2024-03-18 21:02:08",
"bio": null,
"website": null,
"twitter": null,
"github": null,
"youtube": null
},
"sessionToken": "",
"userId": "",
"expires": "2024-04-17T21:02:08.645Z"
}
It took me way too much time to figure that out, it makes no sense.
Adapter from next-auth/adapters is moved to @auth/core/adapters. If you are creating a custom adapter, use @auth/core instead of next-auth.
This is the only relevant ambiguous docs I see, and I fail to understand how can I bypass having to copypaste all the methods I'm not using.
Hey folks, there is a rewrite of the drizzle-adapter in a PR which should be merged shortly. This one doesn't have the table names hardcoded and allows passing custom schemas :heart:
for those who wondering if you just want to retrieve the whole user table content you can setup custom shema like that
import NextAuth from "next-auth"
import Google from "next-auth/providers/google"
import { DrizzleAdapter } from "@auth/drizzle-adapter"
import { db, accounts, sessions, users, verificationTokens } from "./schema"
export const { handlers, auth } = NextAuth({
adapter: DrizzleAdapter(db, {
usersTable: users,
accountsTable: accounts,
sessionsTable: sessions,
verificationTokensTable: verificationTokens,
}),
providers: [Google],
})
see https://authjs.dev/getting-started/adapters/drizzle
for those who wondering if you just want to retrieve the whole user table content you can setup custom shema like that
import NextAuth from "next-auth" import Google from "next-auth/providers/google" import { DrizzleAdapter } from "@auth/drizzle-adapter" import { db, accounts, sessions, users, verificationTokens } from "./schema" export const { handlers, auth } = NextAuth({ adapter: DrizzleAdapter(db, { usersTable: users, accountsTable: accounts, sessionsTable: sessions, verificationTokensTable: verificationTokens, }), providers: [Google], })
see https://authjs.dev/getting-started/adapters/drizzle
This was working for me however it suddenly stopped working for me
Seems to have something to do with the id but not sure really.
for those who wondering if you just want to retrieve the whole user table content you can setup custom shema like that
import NextAuth from "next-auth" import Google from "next-auth/providers/google" import { DrizzleAdapter } from "@auth/drizzle-adapter" import { db, accounts, sessions, users, verificationTokens } from "./schema" export const { handlers, auth } = NextAuth({ adapter: DrizzleAdapter(db, { usersTable: users, accountsTable: accounts, sessionsTable: sessions, verificationTokensTable: verificationTokens, }), providers: [Google], })
see https://authjs.dev/getting-started/adapters/drizzle
This was working for me however it suddenly stopped working for me
Seems to have something to do with the id but not sure really.
@MikeLautensack
Yes after updating dependencies I had the same issue.
Update: A new issue was created about our issue: https://github.com/nextauthjs/next-auth/issues/11490