Last active
May 24, 2021 05:35
-
-
Save Stringsaeed/589c4e9ce3c09ec46079d2549645866a to your computer and use it in GitHub Desktop.
AccountsJs (https://www.accountsjs.com/) Prisma Adapter
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
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; |
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
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