Created
April 28, 2021 10:41
-
-
Save kripod/35ccf5319c8477de6f71c1877c7de320 to your computer and use it in GitHub Desktop.
Works well with next-auth@3.18.0
This file contains 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
import type * as Prisma from "@prisma/client"; | |
import { createHash, randomBytes } from "crypto"; | |
import type { Adapter } from "next-auth/adapters"; | |
import { | |
CreateSessionError, | |
CreateUserError, | |
CreateVerificationRequestError, | |
DeleteSessionError, | |
DeleteUserError, | |
DeleteVerificationRequestError, | |
GetSessionError, | |
GetUserByEmailError, | |
GetUserByIdError, | |
GetUserByProviderAccountIdError, | |
GetVerificationRequestError, | |
LinkAccountError, | |
UnlinkAccountError, | |
UpdateSessionError, | |
UpdateUserError, | |
} from "next-auth/errors"; | |
const defaultSessionMaxAgeMs = 30 * 24 * 60 * 60 * 1000; | |
const defaultSessionUpdateAgeMs = 24 * 60 * 60 * 1000; | |
function verificationRequestToken({ | |
token, | |
secret, | |
}: { | |
token: string; | |
secret: string; | |
}) { | |
// TODO: Use bcrypt or a more secure method | |
return createHash("sha256").update(`${token}${secret}`).digest("hex"); | |
} | |
type PrismaAdapterConfig<P extends Prisma.PrismaClient> = { | |
prisma: P; | |
}; | |
export function PrismaAdapter<P extends Prisma.PrismaClient>({ | |
prisma, | |
}: PrismaAdapterConfig<P>): Adapter { | |
return { | |
getAdapter: async ({ logger, ...appOptions }) => { | |
function debug(debugCode: string, ...args: unknown[]) { | |
logger.debug(`PRISMA_${debugCode}`, ...args); | |
} | |
if (!appOptions.session.maxAge) { | |
debug( | |
"GET_ADAPTER", | |
"Session expiry not configured (defaulting to 30 days)", | |
); | |
} | |
if (!appOptions.session.updateAge) { | |
debug( | |
"GET_ADAPTER", | |
"Session update age not configured (defaulting to 1 day)", | |
); | |
} | |
const sessionMaxAgeMs = appOptions.session.maxAge | |
? appOptions.session.maxAge * 1000 | |
: defaultSessionMaxAgeMs; | |
const sessionUpdateAgeMs = appOptions.session.updateAge | |
? appOptions.session.updateAge * 1000 | |
: defaultSessionUpdateAgeMs; | |
return { | |
createUser: async (profile) => { | |
debug("CREATE_USER", profile); | |
try { | |
return await prisma.user.create({ | |
data: { | |
name: profile.name, | |
email: profile.email, | |
image: profile.image, | |
emailVerified: profile.emailVerified?.toISOString() || null, | |
}, | |
}); | |
} catch (error) { | |
logger.error("CREATE_USER_ERROR", error); | |
throw new CreateUserError(error); | |
} | |
}, | |
getUser: async (id) => { | |
debug("GET_USER_BY_ID", id); | |
try { | |
return await prisma.user.findUnique({ where: { id } }); | |
} catch (error) { | |
logger.error("GET_USER_BY_ID_ERROR", error); | |
throw new GetUserByIdError(error); | |
} | |
}, | |
getUserByEmail: async (email) => { | |
debug("GET_USER_BY_EMAIL", email); | |
try { | |
if (!email) return null; | |
return await prisma.user.findUnique({ where: { email } }); | |
} catch (error) { | |
logger.error("GET_USER_BY_EMAIL_ERROR", error); | |
throw new GetUserByEmailError(error); | |
} | |
}, | |
getUserByProviderAccountId: async (providerId, providerAccountId) => { | |
debug( | |
"GET_USER_BY_PROVIDER_ACCOUNT_ID", | |
providerId, | |
providerAccountId, | |
); | |
try { | |
const account = await prisma.account.findUnique({ | |
where: { | |
providerId_providerAccountId: { providerId, providerAccountId }, | |
}, | |
select: { user: true }, | |
}); | |
return account ? account.user : null; | |
} catch (error) { | |
logger.error("GET_USER_BY_PROVIDER_ACCOUNT_ID_ERROR", error); | |
throw new GetUserByProviderAccountIdError(error); | |
} | |
}, | |
updateUser: async (user) => { | |
debug("UPDATE_USER", user); | |
try { | |
return await prisma.user.update({ | |
where: { id: user.id }, | |
data: { | |
name: user.name, | |
email: user.email, | |
image: user.image, | |
emailVerified: user.emailVerified?.toISOString() || null, | |
}, | |
}); | |
} catch (error) { | |
logger.error("UPDATE_USER_ERROR", error); | |
throw new UpdateUserError(error); | |
} | |
}, | |
/* TODO: Remove type annotations */ | |
deleteUser: async (userId: string) => { | |
debug("DELETE_USER", userId); | |
try { | |
return await prisma.user.delete({ where: { id: userId } }); | |
} catch (error) { | |
logger.error("DELETE_USER_ERROR", error); | |
throw new DeleteUserError(error); | |
} | |
}, | |
linkAccount: async ( | |
userId, | |
providerId, | |
providerType, | |
providerAccountId, | |
refreshToken, | |
accessToken, | |
accessTokenExpires, | |
) => { | |
debug( | |
"LINK_ACCOUNT", | |
userId, | |
providerId, | |
providerType, | |
providerAccountId, | |
refreshToken, | |
accessToken, | |
accessTokenExpires, | |
); | |
try { | |
await prisma.account.create({ | |
data: { | |
userId, | |
providerId, | |
providerType, | |
providerAccountId, | |
refreshToken, | |
accessToken, | |
accessTokenExpires: | |
accessTokenExpires != null | |
? new Date(accessTokenExpires) | |
: null, | |
}, | |
}); | |
} catch (error) { | |
logger.error("LINK_ACCOUNT_ERROR", error); | |
throw new LinkAccountError(error); | |
} | |
}, | |
/* TODO: Remove type annotations */ | |
unlinkAccount: async ( | |
userId: string, | |
providerId: string, | |
providerAccountId: string, | |
) => { | |
debug("UNLINK_ACCOUNT", userId, providerId, providerAccountId); | |
try { | |
return await prisma.account.delete({ | |
where: { | |
providerId_providerAccountId: { providerId, providerAccountId }, | |
}, | |
}); | |
} catch (error) { | |
logger.error("UNLINK_ACCOUNT_ERROR", error); | |
throw new UnlinkAccountError(error); | |
} | |
}, | |
createSession: async (user) => { | |
debug("CREATE_SESSION", user); | |
try { | |
return await prisma.session.create({ | |
data: { | |
userId: user.id, | |
expires: new Date(Date.now() + sessionMaxAgeMs), | |
sessionToken: randomBytes(32).toString("hex"), | |
accessToken: randomBytes(32).toString("hex"), | |
}, | |
}); | |
} catch (error) { | |
logger.error("CREATE_SESSION_ERROR", error); | |
throw new CreateSessionError(error); | |
} | |
}, | |
getSession: async (sessionToken) => { | |
debug("GET_SESSION", sessionToken); | |
try { | |
const session = await prisma.session.findUnique({ | |
where: { sessionToken }, | |
}); | |
if (session && session.expires < new Date()) { | |
await prisma.session.delete({ where: { sessionToken } }); | |
return null; | |
} | |
return session; | |
} catch (error) { | |
logger.error("GET_SESSION_ERROR", error); | |
throw new GetSessionError(error); | |
} | |
}, | |
updateSession: async (session, force) => { | |
debug("UPDATE_SESSION", session); | |
try { | |
if ( | |
!force && | |
Number(session.expires) - sessionMaxAgeMs + sessionUpdateAgeMs > | |
Date.now() | |
) { | |
return null; | |
} | |
return await prisma.session.update({ | |
where: { id: session.id }, | |
data: { | |
expires: new Date(Date.now() + sessionMaxAgeMs), | |
}, | |
}); | |
} catch (error) { | |
logger.error("UPDATE_SESSION_ERROR", error); | |
throw new UpdateSessionError(error); | |
} | |
}, | |
deleteSession: async (sessionToken) => { | |
debug("DELETE_SESSION", sessionToken); | |
try { | |
await prisma.session.delete({ where: { sessionToken } }); | |
} catch (error) { | |
logger.error("DELETE_SESSION_ERROR", error); | |
throw new DeleteSessionError(error); | |
} | |
}, | |
createVerificationRequest: async ( | |
identifier, | |
url, | |
token, | |
secret, | |
provider, | |
) => { | |
debug("CREATE_VERIFICATION_REQUEST", identifier); | |
try { | |
const hashedToken = verificationRequestToken({ token, secret }); | |
const verificationRequest = await prisma.verificationRequest.create( | |
{ | |
data: { | |
identifier, | |
token: hashedToken, | |
expires: new Date( | |
Date.now() + | |
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion | |
provider.maxAge! * 1000, | |
), | |
}, | |
}, | |
); | |
await provider.sendVerificationRequest({ | |
identifier, | |
url, | |
token, | |
baseUrl: appOptions.baseUrl, | |
provider, | |
}); | |
return verificationRequest; | |
} catch (error) { | |
logger.error("CREATE_VERIFICATION_REQUEST_ERROR", error); | |
throw new CreateVerificationRequestError(error); | |
} | |
}, | |
getVerificationRequest: async (identifier, token, secret) => { | |
debug("GET_VERIFICATION_REQUEST", identifier, token); | |
try { | |
const hashedToken = verificationRequestToken({ token, secret }); | |
const verificationRequest = await prisma.verificationRequest.findUnique( | |
{ | |
where: { identifier_token: { identifier, token: hashedToken } }, | |
}, | |
); | |
if ( | |
verificationRequest && | |
verificationRequest.expires < new Date() | |
) { | |
await prisma.verificationRequest.delete({ | |
where: { identifier_token: { identifier, token: hashedToken } }, | |
}); | |
return null; | |
} | |
return verificationRequest; | |
} catch (error) { | |
logger.error("GET_VERIFICATION_REQUEST_ERROR", error); | |
throw new GetVerificationRequestError(error); | |
} | |
}, | |
deleteVerificationRequest: async (identifier, token, secret) => { | |
debug("DELETE_VERIFICATION_REQUEST", identifier, token); | |
try { | |
const hashedToken = verificationRequestToken({ token, secret }); | |
await prisma.verificationRequest.delete({ | |
where: { identifier_token: { identifier, token: hashedToken } }, | |
}); | |
} catch (error) { | |
logger.error("DELETE_VERIFICATION_REQUEST_ERROR", error); | |
throw new DeleteVerificationRequestError(error); | |
} | |
}, | |
}; | |
}, | |
}; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment