Skip to content

Instantly share code, notes, and snippets.

@IzumiSy
Created June 7, 2023 16:02
Show Gist options
  • Save IzumiSy/1c1a68709d6e590f08274fd76e2e23f2 to your computer and use it in GitHub Desktop.
Save IzumiSy/1c1a68709d6e590f08274fd76e2e23f2 to your computer and use it in GitHub Desktop.
PrismaAdapter for NextAuth with all camelCase naming convention
import { Adapter, AdapterSession, AdapterUser } from 'next-auth/adapters'
import { PrismaClient } from '@prisma/client'
import { PrismaClientKnownRequestError } from '@prisma/client/runtime'
import { User, Session } from '@prisma/client'
// PrismaAdapter customized to have more cleaner naming consistency
// (From: https://github.com/nextauthjs/next-auth/blob/main/packages/adapter-prisma/src/index.ts)
export function PrismaAdapter(p: PrismaClient): Adapter {
const toAdapterUser = (user: User): AdapterUser => {
return {
id: user.id,
name: user.name,
email: user.email || '',
emailVerified: user.emailVerified,
image: user.image,
}
}
const toAdapterSession = (session: Session): AdapterSession => {
return {
sessionToken: session.sessionToken,
userId: session.userID,
expires: session.expires,
}
}
return {
// users
createUser: (data) => p.user.create({ data }).then(toAdapterUser),
async getUser(id) {
const user = await p.user.findUnique({ where: { id } })
if (user === null) return null
return toAdapterUser(user)
},
async getUserByEmail(email) {
const user = await p.user.findUnique({ where: { email } })
if (user === null) return null
return toAdapterUser(user)
},
async getUserByAccount({ provider, providerAccountId }) {
const account = await p.account.findUnique({
include: { user: true },
where: {
provider_providerAccountID: {
provider,
providerAccountID: providerAccountId,
},
},
})
if (account === null) return null
return toAdapterUser(account.user)
},
async updateUser({ id, ...data }) {
return await p.user.update({ where: { id }, data }).then(toAdapterUser)
},
async deleteUser(id) {
await p.user.delete({ where: { id } })
},
// accounts
async linkAccount(data) {
await p.account.create({
data: {
userID: data.userId,
type: data.type,
provider: data.provider,
providerAccountID: data.providerAccountId,
refreshToken: data.refresh_token,
accessToken: data.access_token,
expiresAt: data.expires_at,
tokenType: data.token_type,
scope: data.scope,
idToken: data.id_token,
sessionState: data.session_state,
},
})
return data
},
async unlinkAccount({ provider, providerAccountId }) {
await p.account.delete({
where: {
provider_providerAccountID: {
provider,
providerAccountID: providerAccountId,
},
},
})
},
// sessions
async getSessionAndUser(sessionToken) {
const userAndSession = await p.session.findUnique({
where: { sessionToken },
include: { user: true },
})
if (!userAndSession) return null
const { user, ...session } = userAndSession
return {
user: toAdapterUser(user),
session: toAdapterSession(session),
}
},
createSession: (data) =>
p.session
.create({
data: {
sessionToken: data.sessionToken,
userID: data.userId,
expires: data.expires,
},
})
.then(toAdapterSession),
updateSession: (data) =>
p.session
.update({
where: { sessionToken: data.sessionToken },
data,
})
.then(toAdapterSession),
async deleteSession(sessionToken) {
await p.session.delete({ where: { sessionToken } })
},
async createVerificationToken(data) {
const verificationToken = await p.verificationToken.create({ data })
// @ts-expect-errors // MongoDB needs an ID, but we don't
if (verificationToken.id) delete verificationToken.id
return verificationToken
},
async useVerificationToken(identifier_token) {
try {
const verificationToken = await p.verificationToken.delete({
where: { identifier_token },
})
// @ts-expect-errors // MongoDB needs an ID, but we don't
if (verificationToken.id) delete verificationToken.id
return verificationToken
} catch (error) {
// If token already used/deleted, just return null
// https://www.prisma.io/docs/reference/api-reference/error-reference#p2025
if ((error as PrismaClientKnownRequestError).code === 'P2025')
return null
throw error
}
},
}
}
model Account {
id String @id @default(cuid())
userID String @map("user_id")
type String
provider String
providerAccountID String @map("provider_account_id")
refreshToken String? @map("refresh_token") @db.String()
accessToken String? @map("access_token") @db.String()
expiresAt Int? @map("expires_at")
tokenType String? @map("token_type")
scope String?
idToken String? @map("id_token") @db.String()
sessionState String? @map("session_state")
user User @relation(fields: [userID], references: [id], onDelete: Cascade)
@@unique([provider, providerAccountID])
@@map("accounts")
}
model Session {
id String @id @default(cuid())
sessionToken String @unique @map("session_token")
userID String @map("user_id")
expires DateTime
user User @relation(fields: [userID], references: [id], onDelete: Cascade)
@@map("sessions")
}
model User {
id String @id @default(cuid())
name String?
email String? @unique
emailVerified DateTime? @map("email_verified")
image String?
Session Session[]
Account Account[]
@@map("users")
}
model VerificationToken {
identifier String
token String @unique
expires DateTime
@@unique([identifier, token])
@@map("verificationTokens")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment