Skip to content

Instantly share code, notes, and snippets.

@jasonkuhrt
Last active March 2, 2021 21:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jasonkuhrt/19a18ae7df9049dc7bd5c474254db769 to your computer and use it in GitHub Desktop.
Save jasonkuhrt/19a18ae7df9049dc7bd5c474254db769 to your computer and use it in GitHub Desktop.
prisma adaptor for next-auth with cuid
/**
* Adapted from https://github.com/nextauthjs/adapters/tree/canary/packages/prisma
*/
import * as Prisma from '@prisma/client'
import { User } from '@prisma/client'
import { AppOptions } from 'next-auth'
// @ts-expect-error TODO expose errors in next-auth
import { CreateUserError } from 'next-auth/dist/lib/errors'
// @ts-expect-error TODO expose logger in next-auth
import logger from 'next-auth/dist/lib/logger'
import { NextAuthProfile } from '~/utils/nextAuth'
function debug(debugCode: string, ...args: any) {
logger.debug(`PRISMA_${debugCode}`, ...args)
}
type UserId = Prisma.User['id']
type IsValid<
T extends Prisma.PrismaClient,
U extends keyof T
> = RequiredMethods extends keyof T[U]
? T[U][RequiredMethods] extends (args?: any) => any
? 1
: 0
: 0
type RequiredMethods = 'create' | 'findUnique' | 'delete' | 'update'
type Filter<T extends Prisma.PrismaClient> = {
[K in keyof T]-?: {
1: K
0: never
}[IsValid<T, K>]
}[keyof T]
export default function PrismaAdapter<
T extends Prisma.PrismaClient,
U extends Filter<T> extends string ? Filter<T> : never,
A extends Filter<T>,
S extends Filter<T>,
VR extends Filter<T>
>({
prisma,
modelMapping,
}: {
prisma: T
modelMapping?: {
User: U
Account: A
Session: S
VerificationRequest: VR
}
}) {
const modelMap = (modelMapping ?? {
User: 'user',
Account: 'account',
Session: 'session',
VerificationRequest: 'verificationRequest',
}) as {
User: 'user'
Account: 'account'
Session: 'session'
VerificationRequest: 'verificationRequest'
}
async function getAdapter(appOptions?: Partial<AppOptions>) {
if (!appOptions?.session?.maxAge) {
debug(
'GET_ADAPTER',
'Session expiry not configured (defaulting to 30 days'
)
}
async function createUser(profile: NextAuthProfile) {
debug('CREATE_USER', profile)
try {
return prisma[modelMap.User].create({
data: {
email: profile.email,
handle: profile.handle,
displayName: profile.displayName,
image: profile.image,
},
})
} catch (error) {
logger.error('CREATE_USER_ERROR', error)
throw CreateUserError(error)
}
}
async function getUser(id: UserId) {
debug('GET_USER', id)
try {
return prisma[modelMap.User].findUnique({
where: {
id,
},
})
} catch (error) {
logger.error('GET_USER_BY_ID_ERROR', error)
// @ts-expect-error wtf?
throw new Error('GET_USER_BY_ID_ERROR', error)
}
}
async function getUserByEmail(email: string) {
debug('GET_USER_BY_EMAIL', email)
try {
return prisma[modelMap.User].findUnique({
where: {
email,
},
})
} catch (error) {
logger.error('GET_USER_BY_EMAIL_ERROR', error)
// @ts-expect-error wtf?
throw new Error('GET_USER_BY_EMAIL_ERROR', error)
}
}
async function getUserByProviderAccountId(
providerId: string,
providerAccountId: string
) {
debug('GET_USER_BY_PROVIDER_ACCOUNT_ID', providerId, providerAccountId)
if (!providerId || !providerAccountId) {
return null
}
try {
const account = await prisma[modelMap.Account].findUnique({
where: {
providerId_providerAccountId: {
providerId: providerId,
providerAccountId: String(providerAccountId),
},
},
include: {
user: true,
},
})
return account?.user ?? null
} catch (error) {
logger.error('GET_USER_BY_PROVIDER_ACCOUNT_ID_ERROR', error)
// @ts-expect-error wtf?
throw new Error('GET_USER_BY_PROVIDER_ACCOUNT_ID_ERROR', error)
}
}
async function updateUser(user: User) {
debug('UPDATE_USER', user)
try {
const { id, displayName, email, image } = user
return prisma[modelMap.User].update({
where: { id },
data: {
displayName,
email,
image,
},
})
} catch (error) {
logger.error('UPDATE_USER_ERROR', error)
// @ts-expect-error wtf?
throw new Error('UPDATE_USER_ERROR', error)
}
}
async function deleteUser(userId: UserId) {
debug('DELETE_USER', userId)
try {
return prisma[modelMap.User].delete({ where: { id: userId } })
} catch (error) {
logger.error('DELETE_USER_ERROR', error)
// @ts-expect-error wtf?
throw new Error('DELETE_USER_ERROR', error)
}
}
async function linkAccount(
userId: UserId,
providerId: string,
providerType: string,
providerAccountId: string,
refreshToken: string,
accessToken: string,
accessTokenExpires: string | Date | null
) {
debug(
'LINK_ACCOUNT',
userId,
providerId,
providerType,
providerAccountId,
refreshToken,
accessToken,
accessTokenExpires
)
try {
return prisma[modelMap.Account].create({
data: {
accessToken,
refreshToken,
providerAccountId: `${providerAccountId}`,
providerId,
providerType,
accessTokenExpires,
user: { connect: { id: userId } },
},
})
} catch (error) {
logger.error('LINK_ACCOUNT_ERROR', error)
// @ts-expect-error wtf?
throw new Error('LINK_ACCOUNT_ERROR', error)
}
}
async function unlinkAccount(
userId: string,
providerId: string,
providerAccountId: string
) {
debug('UNLINK_ACCOUNT', userId, providerId, providerAccountId)
try {
return prisma[modelMap.Account].delete({
where: {
providerId_providerAccountId: {
providerAccountId: String(providerAccountId),
providerId: providerId,
},
},
})
} catch (error) {
logger.error('UNLINK_ACCOUNT_ERROR', error)
// @ts-expect-error wtf?
throw new Error('UNLINK_ACCOUNT_ERROR', error)
}
}
async function createSession() {
throw new Error(`Cannot run createSession because session is disabled`)
}
async function getSession() {
// throw new Error(`Cannot run getSession because session is disabled`)
return null
}
async function updateSession() {
throw new Error(`Cannot run updateSession because session is disabled`)
}
async function deleteSession() {
throw new Error(`Cannot run deleteSession because session is disabled`)
}
async function createVerificationRequest() {
throw new Error(
`Cannot run createVerificationRequest because verification request is disabled`
)
}
async function getVerificationRequest() {
throw new Error(
`Cannot run getVerificationRequest because verification request is disabled`
)
}
async function deleteVerificationRequest() {
throw new Error(
`Cannot run deleteVerificationRequest because verification request is disabled`
)
}
return {
createUser,
getUser,
getUserByEmail,
getUserByProviderAccountId,
updateUser,
deleteUser,
linkAccount,
unlinkAccount,
createSession,
getSession,
updateSession,
deleteSession,
createVerificationRequest,
getVerificationRequest,
deleteVerificationRequest,
}
}
return {
getAdapter,
}
}
datasource db {
provider = "postgresql"
url = env("DB_URL")
}
model User {
id String @id @default(cuid())
displayName String
email String @unique
image String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
accounts Account[]
Project Project[]
}
// Account
enum AccountKind {
account
}
model Account {
id String @id @default(cuid())
userId String
providerType String
providerId String
providerAccountId String
refreshToken String?
accessToken String?
accessTokenExpires DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id])
@@unique([providerId, providerAccountId])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment