UNPKG

@accounts/mongo-magic-link

Version:
121 lines (110 loc) 3.57 kB
import { type Collection, type Db, type CreateIndexesOptions } from 'mongodb'; import { type DatabaseInterfaceServiceMagicLink, type User } from '@accounts/types'; import { toMongoID } from './utils'; type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; export interface MongoServiceMagicLinkOptions { /** * Mongo database object. */ database: Db; /** * The users collection name. * Default 'users'. */ userCollectionName?: string; /** * Should the user collection use _id as string or ObjectId. * Default 'true'. */ convertUserIdToMongoObjectId?: boolean; /** * Function that generate the date for the timestamps. * Default to `(date?: Date) => (date ? date.getTime() : Date.now())`. */ dateProvider?: (date?: Date) => any; } const defaultOptions = { userCollectionName: 'users', convertUserIdToMongoObjectId: true, dateProvider: (date?: Date) => (date ? date.getTime() : Date.now()), }; export class MongoServiceMagicLink implements DatabaseInterfaceServiceMagicLink { // Merged options that can be used private options: MongoServiceMagicLinkOptions & typeof defaultOptions; // Mongo database object private database: Db; // Mongo user collection private userCollection: Collection< PartialBy<User & { _id?: string | object }, 'id' | 'deactivated'> >; constructor(options: MongoServiceMagicLinkOptions) { this.options = { ...defaultOptions, ...options, }; this.database = this.options.database; this.userCollection = this.database.collection(this.options.userCollectionName); } /** * Setup the mongo indexes needed for the token service. * @param options Options passed to the mongo native `createIndex` method. */ public async setupIndexes( options: Omit<CreateIndexesOptions, 'unique' | 'sparse'> = {} ): Promise<void> { // Token index used to verify the email address of a user await this.userCollection.createIndex('services.magicLink.loginTokens.token', { ...options, sparse: true, }); } /** * Find a user from a login token. * @param token Random token used to allow user to login. */ public async findUserByLoginToken(token: string): Promise<User | null> { const user = await this.userCollection.findOne({ 'services.magicLink.loginTokens.token': token, }); if (user) { user.id = user._id.toString(); } return user as User; } /** * Add a login token to a user. * @param userId Id used to update the user. * @param email Which address of the user's to link the token to. * @param token Random token used to allow user to login. */ public async addLoginToken(userId: string, email: string, token: string): Promise<void> { const _id = this.options.convertUserIdToMongoObjectId ? toMongoID(userId) : userId; await this.userCollection.updateOne( { _id }, { $push: { 'services.magicLink.loginTokens': { token, address: email.toLowerCase(), when: this.options.dateProvider(), }, }, } ); } /** * Remove all the login tokens for a user. * @param userId Id used to update the user. */ public async removeAllLoginTokens(userId: string): Promise<void> { const id = this.options.convertUserIdToMongoObjectId ? toMongoID(userId) : userId; await this.userCollection.updateOne( { _id: id }, { $unset: { 'services.magicLink.loginTokens': '', }, } ); } }