Skip to content

Instantly share code, notes, and snippets.

@Stringsaeed
Last active May 24, 2021 05:35
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 Stringsaeed/589c4e9ce3c09ec46079d2549645866a to your computer and use it in GitHub Desktop.
Save Stringsaeed/589c4e9ce3c09ec46079d2549645866a to your computer and use it in GitHub Desktop.
AccountsJs (https://www.accountsjs.com/) Prisma Adapter
import {
DatabaseInterface,
ConnectionInformations,
CreateUser,
User as AccountsUser,
} from '@accounts/types';
import {
PrismaClient,
Prisma,
User,
UserService,
UserEmail,
} from '@prisma/client';
const prisma = new PrismaClient();
class DatabaseProvider implements DatabaseInterface {
private static normalizeUser({
username,
...user
}: User & { services?: UserService[]; emails?: UserEmail[] }) {
return {
...user,
username: username ?? undefined,
};
}
public async findUserByEmail(email: string) {
const userEmail = await prisma.userEmail.findUnique({
where: { address: email },
include: { User: true },
});
if (userEmail?.User) {
return DatabaseProvider.normalizeUser(userEmail.User);
}
return null;
}
public async findUserByUsername(username: string) {
const user = await prisma.user.findUnique({
where: { username },
});
if (user) {
return DatabaseProvider.normalizeUser(user);
}
return null;
}
public async findUserById(userId: string) {
const user = await prisma.user.findUnique({
where: { id: userId },
include: { services: true, emails: true },
});
if (user) {
return DatabaseProvider.normalizeUser(user);
}
return null;
}
public async findUserByResetPasswordToken(token: string) {
const service = await prisma.userService.findFirst({
where: {
name: 'password.reset',
token,
},
});
if (service) {
return this.findUserById(service.userId);
}
return null;
}
public async findUserByEmailVerificationToken(token: string) {
const service = await prisma.userService.findFirst({
where: {
name: 'email.verificationTokens',
token,
},
});
if (service) {
return this.findUserById(service.userId);
}
return null;
}
public async createUser(createUser: CreateUser): Promise<string> {
const { username, email, password, ...otherFields } = createUser;
const user = await prisma.user.create({
data: {
...otherFields,
username,
...(email && {
emails: { create: { address: email, verified: false } },
}),
services: {
create: { name: 'password', options: { bcrypt: password } },
},
},
});
return user.id;
}
public async setUsername(userId: string, newUsername: string): Promise<void> {
const user = await this.findUserById(userId);
if (user) {
await prisma.user.update({
where: { id: userId },
data: { username: newUsername },
});
return;
}
throw new Error('User not found');
}
public async findUserByServiceId(serviceName: string, serviceId: string) {
const user = await prisma.user.findFirst({
where: { services: { every: { serviceId, name: serviceName } } },
});
if (user) {
return DatabaseProvider.normalizeUser(user);
}
return null;
}
public async getService(userId: string, serviceName: string) {
const user = await this.findUserById(userId);
if (user) {
const service = user?.services?.find((s) => s.name === serviceName);
if (service) {
return service;
}
}
return null;
}
public async setService(
userId: string,
serviceName: string,
data: any,
token?: string,
) {
const { id = null, ...options } = data as any;
await prisma.userService.upsert({
where: { name_userId: { name: serviceName, userId } },
update: {
options,
token,
serviceId: id,
},
create: {
name: serviceName,
userId: userId,
options,
token,
serviceId: id,
},
});
}
public async unsetService(
userId: string,
serviceName: string,
): Promise<void> {
await prisma.userService.delete({
where: {
name_userId: { name: serviceName, userId },
},
});
}
public async findPasswordHash(userId: string): Promise<string | null> {
const service = await this.getService(userId, 'password');
if (service) {
return ((service.options as Prisma.JsonObject)?.bcrypt as string) || '';
}
return null;
}
public async setPassword(userId: string, newPassword: string): Promise<void> {
const user = await this.findUserById(userId);
if (user) {
await this.setService(userId, 'password', { bcrypt: newPassword });
return;
}
throw new Error('User not found');
}
public async addResetPasswordToken(
userId: string,
email: string,
token: string,
reason: string,
): Promise<void> {
await this.setService(
userId,
'password.reset',
{
address: email.toLocaleLowerCase(),
when: new Date().toJSON(),
reason,
},
token,
);
}
public async addEmail(
userId: string,
newEmail: string,
verified: boolean,
): Promise<void> {
const user = await prisma.user.update({
where: { id: userId },
data: {
emails: {
create: {
address: newEmail.toLowerCase(),
verified,
},
},
},
});
if (user) {
return;
}
throw new Error('User not found');
}
public async removeEmail(userId: string, email: string): Promise<void> {
const userEmail = await prisma.userEmail.delete({
where: { userId_address: { userId, address: email } },
});
if (userEmail) {
return;
}
throw new Error('User not found');
}
public async verifyEmail(userId: string, email: string): Promise<void> {
const user = await prisma.userEmail.update({
where: { userId_address: { userId, address: email } },
data: { verified: true },
});
if (user) {
await this.unsetService(userId, 'email.verificationTokens');
return;
}
throw new Error('User not found');
}
public async addEmailVerificationToken(
userId: string,
email: string,
token: string,
): Promise<void> {
await this.setService(
userId,
'email.verificationTokens',
{
address: email.toLocaleLowerCase(),
when: new Date(),
},
token,
);
}
public async removeAllResetPasswordTokens(userId: string): Promise<void> {
await this.unsetService(userId, 'password.reset');
}
public async setUserDeactivated(
userId: string,
deactivated: boolean,
): Promise<void> {
await prisma.user.update({ where: { id: userId }, data: { deactivated } });
}
public async findSessionById(sessionId: string) {
try {
const session = await prisma.userSession.findFirst({
where: { id: sessionId },
});
if (session) {
return {
...session,
createdAt: session.createdAt.toDateString(),
updatedAt: session.updatedAt.toDateString(),
};
}
} catch (err) {
// noop
}
return null;
}
public async findSessionByToken(token: string) {
const session = await prisma.userSession.findFirst({
where: { token },
});
if (!session) {
return null;
}
return {
...session,
createdAt: session.createdAt.toDateString(),
updatedAt: session.updatedAt.toDateString(),
};
}
public async createSession(
userId: string,
token: string,
connection: ConnectionInformations = {},
extra?: object,
) {
const session = await prisma.userSession.create({
data: {
token,
userAgent: connection.userAgent,
ip: connection.ip,
extra,
valid: true,
userId,
},
});
return session.id;
}
public async updateSession(
sessionId: string,
connection: ConnectionInformations,
): Promise<void> {
await prisma.userSession.update({
where: { id: sessionId },
data: {
userAgent: connection.userAgent,
ip: connection.ip,
},
});
}
public async invalidateSession(sessionId: string): Promise<void> {
await prisma.userSession.update({
where: { id: sessionId },
data: {
valid: false,
},
});
}
public async invalidateAllSessions(
userId: string,
excludedSessionIds?: string[],
): Promise<void> {
await prisma.userSession.updateMany({
where: {
...(excludedSessionIds && { NOT: { id: { in: excludedSessionIds } } }),
userId,
},
data: { valid: false },
});
}
}
export default DatabaseProvider;
model User {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
username String? @unique
deactivated Boolean @default(false)
services UserService[]
emails UserEmail[]
sessions UserSession[]
}
model UserEmail {
id String @id @default(uuid())
address String @unique
verified Boolean @default(false)
User User? @relation(fields: [userId], references: [id])
userId String
@@unique([userId, address])
}
model UserService {
id String @id @default(uuid())
name String
token String?
userId String
serviceId String? @unique
options Json?
User User @relation(fields: [userId], references: [id])
@@unique([name, userId])
}
model UserSession {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
token String @unique
valid Boolean
userAgent String? @db.Text
ip String? @db.Text
extra Json?
userId String
User User @relation(fields: [userId], references: [id])
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment