UNPKG

@accounts/mongo

Version:
257 lines (223 loc) 8.38 kB
import { type Collection, type Db, ObjectId, type CreateIndexesOptions } from 'mongodb'; import { type ConnectionInformations, type CreateUserServicePassword, type DatabaseInterface, type User, type Session, } from '@accounts/types'; import { MongoSessions } from '@accounts/mongo-sessions'; import { MongoServicePassword } from '@accounts/mongo-password'; import { MongoServiceMagicLink } from '@accounts/mongo-magic-link'; import { AccountsMongoConfigToken, type AccountsMongoOptions } from './types'; import { MongoConnectionToken } from './types/MongoConnection.symbol'; import { Inject, Injectable } from 'graphql-modules'; type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; const toMongoID = (objectId: string | ObjectId) => { if (typeof objectId === 'string') { return new ObjectId(objectId); } return objectId; }; const defaultOptions = { collectionName: 'users', timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt', }, convertUserIdToMongoObjectId: true, convertSessionIdToMongoObjectId: true, caseSensitiveUserName: true, dateProvider: (date?: Date) => (date ? date.getTime() : Date.now()), }; @Injectable({ global: true, }) export class Mongo implements DatabaseInterface { // Options of Mongo class private options: AccountsMongoOptions & typeof defaultOptions; // Database connection private dbConn: Db; // Account collection private collection: Collection<PartialBy<User & { _id?: string | object }, 'id' | 'deactivated'>>; // Session adaptor private sessions: MongoSessions; // Password service private servicePassword: MongoServicePassword; // Magic link service private serviceMagicLink: MongoServiceMagicLink; constructor( @Inject(MongoConnectionToken) dbConn: any, @Inject(AccountsMongoConfigToken) options: AccountsMongoOptions = {} ) { this.options = { ...defaultOptions, ...options, timestamps: { ...defaultOptions.timestamps, ...options.timestamps }, }; if (!dbConn) { throw new Error('A database connection is required'); } this.dbConn = dbConn; this.collection = this.dbConn.collection(this.options.collectionName); this.sessions = new MongoSessions({ ...this.options, database: this.dbConn }); this.servicePassword = new MongoServicePassword({ ...this.options, userCollectionName: this.options.collectionName, database: this.dbConn, }); this.serviceMagicLink = new MongoServiceMagicLink({ ...this.options, userCollectionName: this.options.collectionName, database: this.dbConn, }); } /** * Setup the mongo indexes needed. * @param options Options passed to the mongo native `createIndex` method. */ public async setupIndexes( options: Omit<CreateIndexesOptions, 'unique' | 'sparse'> = {} ): Promise<void> { await this.sessions.setupIndexes(options); await this.servicePassword.setupIndexes(options); await this.serviceMagicLink.setupIndexes(options); } public async findUserById(userId: string): Promise<User | null> { return this.servicePassword.findUserById(userId); } public async createUser(user: CreateUserServicePassword): Promise<string> { return this.servicePassword.createUser(user); } public async findUserByEmail(email: string): Promise<User | null> { return this.servicePassword.findUserByEmail(email); } public async findUserByUsername(username: string): Promise<User | null> { return this.servicePassword.findUserByUsername(username); } public async findPasswordHash(userId: string): Promise<string | null> { return this.servicePassword.findPasswordHash(userId); } public async findUserByEmailVerificationToken(token: string): Promise<User | null> { return this.servicePassword.findUserByEmailVerificationToken(token); } public async findUserByResetPasswordToken(token: string): Promise<User | null> { return this.servicePassword.findUserByResetPasswordToken(token); } public async addEmail(userId: string, newEmail: string, verified: boolean): Promise<void> { return this.servicePassword.addEmail(userId, newEmail, verified); } public async removeEmail(userId: string, email: string): Promise<void> { return this.servicePassword.removeEmail(userId, email); } public async verifyEmail(userId: string, email: string): Promise<void> { return this.servicePassword.verifyEmail(userId, email); } public async setUsername(userId: string, newUsername: string): Promise<void> { return this.servicePassword.setUsername(userId, newUsername); } public async setPassword(userId: string, newPassword: string): Promise<void> { return this.servicePassword.setPassword(userId, newPassword); } public async removeAllResetPasswordTokens(userId: string): Promise<void> { return this.servicePassword.removeAllResetPasswordTokens(userId); } public async addEmailVerificationToken( userId: string, email: string, token: string ): Promise<void> { return this.servicePassword.addEmailVerificationToken(userId, email, token); } public async addResetPasswordToken( userId: string, email: string, token: string, reason: string ): Promise<void> { return this.servicePassword.addResetPasswordToken(userId, email, token, reason); } public async addLoginToken(userId: string, email: string, token: string): Promise<void> { return this.serviceMagicLink.addLoginToken(userId, email, token); } public async findUserByLoginToken(token: string): Promise<User | null> { return this.serviceMagicLink.findUserByLoginToken(token); } public async removeAllLoginTokens(userId: string): Promise<void> { return this.serviceMagicLink.removeAllLoginTokens(userId); } public async createSession( userId: string, token: string, connection: ConnectionInformations = {}, extraData?: object ): Promise<string> { return this.sessions.createSession(userId, token, connection, extraData); } public async findSessionById(sessionId: string): Promise<Session | null> { return this.sessions.findSessionById(sessionId); } public async findSessionByToken(token: string): Promise<Session | null> { return this.sessions.findSessionByToken(token); } public async updateSession( sessionId: string, connection: ConnectionInformations, newToken?: string ): Promise<void> { return this.sessions.updateSession(sessionId, connection, newToken); } public async invalidateSession(sessionId: string): Promise<void> { return this.sessions.invalidateSession(sessionId); } public async invalidateAllSessions(userId: string, excludedSessionIds?: string[]): Promise<void> { return this.sessions.invalidateAllSessions(userId, excludedSessionIds); } public async findUserByServiceId(serviceName: string, serviceId: string): Promise<User | null> { const user = await this.collection.findOne({ [`services.${serviceName}.id`]: serviceId, }); if (user) { user.id = user._id.toString(); } return user as User; } public async setService(userId: string, serviceName: string, service: object): Promise<void> { const id = this.options.convertUserIdToMongoObjectId ? toMongoID(userId) : userId; await this.collection.updateOne( { _id: id }, { $set: { [`services.${serviceName}`]: service, [this.options.timestamps.updatedAt]: this.options.dateProvider(), }, } ); } public async unsetService(userId: string, serviceName: string): Promise<void> { const id = this.options.convertUserIdToMongoObjectId ? toMongoID(userId) : userId; await this.collection.updateOne( { _id: id }, { $set: { [this.options.timestamps.updatedAt]: this.options.dateProvider(), }, $unset: { [`services.${serviceName}`]: '', }, } ); } public async setUserDeactivated(userId: string, deactivated: boolean): Promise<void> { const id = this.options.convertUserIdToMongoObjectId ? toMongoID(userId) : userId; await this.collection.updateOne( { _id: id }, { $set: { deactivated, [this.options.timestamps.updatedAt]: this.options.dateProvider(), }, } ); } }