Last active
March 2, 2021 21:19
-
-
Save jasonkuhrt/19a18ae7df9049dc7bd5c474254db769 to your computer and use it in GitHub Desktop.
prisma adaptor for next-auth with cuid
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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, | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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