next-auth icon indicating copy to clipboard operation
next-auth copied to clipboard

drizzle-adapter with custom user table?

Open socketopp opened this issue 1 year ago • 6 comments

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?

socketopp avatar Dec 25 '23 15:12 socketopp

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

riccardoperra avatar Dec 30 '23 10:12 riccardoperra

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

gabrielgrover avatar Jan 13 '24 19:01 gabrielgrover

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 avatar Jan 13 '24 23:01 socketopp

@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

nahtnam avatar Jan 25 '24 02:01 nahtnam

@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

I am not sure I follow. The name of the table is 'user' and the variable is named users.

socketopp avatar Jan 25 '24 08:01 socketopp

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 avatar Feb 09 '24 01:02 JavanFriedel

@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 avatar Mar 18 '24 19:03 adriangalilea

@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 avatar Mar 18 '24 20:03 JavanFriedel

@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.

link

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.

adriangalilea avatar Mar 18 '24 21:03 adriangalilea

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:

ndom91 avatar Apr 12 '24 12:04 ndom91

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

ldenblyd avatar Jul 28 '24 19:07 ldenblyd

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

image

image

image

Seems to have something to do with the id but not sure really.

MikeLautensack avatar Aug 03 '24 01:08 MikeLautensack

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

image

image

image

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

josephworks avatar Aug 06 '24 08:08 josephworks