/**
*
*
* ## Installation
*
* ```bash npm2yarn2pnpm
* npm install next-auth drizzle-orm @auth/drizzle-adapter
* npm install drizzle-kit --save-dev
* ```
*
* @module @auth/drizzle-adapter
*/
import type { Adapter } from "@auth/core/adapters";
import { and, eq } from "drizzle-orm";
import type { DbClient, Schema } from "./schema";
/**
* ## Setup
*
* Add this adapter to your `pages/api/[...nextauth].js` next-auth configuration object:
*
* ```js title="pages/api/auth/[...nextauth].js"
* import NextAuth from "next-auth"
* import GoogleProvider from "next-auth/providers/google"
* import { DrizzleAdapter } from "@auth/drizzle-adapter"
* import { db } from "./db-schema"
*
* export default NextAuth({
* adapter: DrizzleAdapter(db),
* providers: [
* GoogleProvider({
* clientId: process.env.GOOGLE_CLIENT_ID,
* clientSecret: process.env.GOOGLE_CLIENT_SECRET,
* }),
* ],
* })
* ```
*
* ## Advanced usage
*
* ### Create the Drizzle schema from scratch
*
* You'll need to create a database schema that includes the minimal schema for a `next-auth` adapter.
* Be sure to use the Drizzle driver version that you're using for your project.
*
* > This schema is adapted for use in Drizzle and based upon our main [schema](https://authjs.dev/reference/adapters#models)
*
*
* ```json title="db-schema.ts"
*
* import { integer, pgTable, text, primaryKey } from 'drizzle-orm/pg-core';
* import { drizzle } from 'drizzle-orm/node-postgres';
* import { migrate } from 'drizzle-orm/node-postgres/migrator';
* import { Pool } from 'pg'
* import { ProviderType } from 'next-auth/providers';
*
* export const users = pgTable('users', {
* id: text('id').notNull().primaryKey(),
* name: text('name'),
* email: text("email").notNull(),
* emailVerified: integer("emailVerified"),
* image: text("image"),
* });
*
* export const accounts = pgTable("accounts", {
* userId: text("userId").notNull().references(() => users.id, { onDelete: "cascade" }),
* type: text("type").$type().notNull(),
* provider: text("provider").notNull(),
* providerAccountId: text("providerAccountId").notNull(),
* refresh_token: text("refresh_token"),
* access_token: text("access_token"),
* expires_at: integer("expires_at"),
* token_type: text("token_type"),
* scope: text("scope"),
* id_token: text("id_token"),
* session_state: text("session_state"),
* }, (account) => ({
* _: primaryKey(account.provider, account.providerAccountId)
* }))
*
* export const sessions = pgTable("sessions", {
* userId: text("userId").notNull().references(() => users.id, { onDelete: "cascade" }),
* sessionToken: text("sessionToken").notNull().primaryKey(),
* expires: integer("expires").notNull(),
* })
*
* export const verificationTokens = pgTable("verificationToken", {
* identifier: text("identifier").notNull(),
* token: text("token").notNull(),
* expires: integer("expires").notNull()
* }, (vt) => ({
* _: primaryKey(vt.identifier, vt.token)
* }))
*
* const pool = new Pool({
* connectionString: "YOUR_CONNECTION_STRING"
* });
*
* export const db = drizzle(pool);
*
* migrate(db, { migrationsFolder: "./drizzle" })
*
* ```
*
**/
export function PgAdapter(
client: DbClient,
{ users, sessions, verificationTokens, accounts }: Schema
): Adapter {
return {
createUser: async (data) => {
return client
.insert(users)
.values({ ...data, id: crypto.randomUUID() as string })
.returning()
.then((res) => res[0]);
},
getUser: async (data) => {
return (
client
.select()
.from(users)
.where(eq(users.id, data))
.then((res) => res[0]) ?? null
);
},
getUserByEmail: async (data) => {
return (
client
.select()
.from(users)
.where(eq(users.email, data))
.then((res) => res[0]) ?? null
);
},
createSession: async (data) => {
return client
.insert(sessions)
.values(data)
.returning()
.then((res) => res[0]);
},
getSessionAndUser: async (data) => {
return (
client
.select({
session: sessions,
user: users,
})
.from(sessions)
.where(eq(sessions.sessionToken, data))
.innerJoin(users, eq(users.id, sessions.userId))
.then((res) => res[0]) ?? null
);
},
updateUser: async (data) => {
if (!data.id) {
throw new Error("No user id.");
}
return client
.update(users)
.set(data)
.where(eq(users.id, data.id))
.returning()
.then((res) => res[0]);
},
updateSession: async (data) => {
return client
.update(sessions)
.set(data)
.where(eq(sessions.sessionToken, data.sessionToken))
.returning()
.then((res) => res[0]);
},
linkAccount: async (rawAccount) => {
const updatedAccount = await client
.insert(accounts)
.values(rawAccount)
.returning()
.then((res) => res[0]);
// Drizzle will return `null` for fields that are not defined.
// However, the return type is expecting `undefined`.
const account = {
...updatedAccount,
access_token: updatedAccount.access_token ?? undefined,
token_type: updatedAccount.token_type ?? undefined,
id_token: updatedAccount.id_token ?? undefined,
refresh_token: updatedAccount.refresh_token ?? undefined,
scope: updatedAccount.scope ?? undefined,
expires_at: updatedAccount.expires_at ?? undefined,
session_state: updatedAccount.session_state ?? undefined,
};
return account;
},
getUserByAccount: async (account) => {
const dbAccount = await client
.select()
.from(accounts)
.where(
and(
eq(accounts.providerAccountId, account.providerAccountId),
eq(accounts.provider, account.provider)
)
)
.leftJoin(users, eq(accounts.userId, users.id))
.then((res) => res[0]);
return dbAccount.users;
},
deleteSession: async (sessionToken) => {
await client
.delete(sessions)
.where(eq(sessions.sessionToken, sessionToken));
},
createVerificationToken: async (token) => {
return client
.insert(verificationTokens)
.values(token)
.returning()
.then((res) => res[0]);
},
useVerificationToken: async (token) => {
try {
return (
client
.delete(verificationTokens)
.where(
and(
eq(verificationTokens.identifier, token.identifier),
eq(verificationTokens.token, token.token)
)
)
.returning()
.then((res) => res[0]) ?? null
);
} catch (err) {
throw new Error("No verification token found.");
}
},
deleteUser: async (id) => {
await client
.delete(users)
.where(eq(users.id, id))
.returning()
.then((res) => res[0]);
},
unlinkAccount: async (account) => {
await client
.delete(accounts)
.where(
and(
eq(accounts.providerAccountId, account.providerAccountId),
eq(accounts.provider, account.provider)
)
);
return undefined;
},
};
}