UNPKG

@tmlmobilidade/interfaces

Version:

This package provides SDK-style connectors for interacting with databases (e.g., stops, plans, rides, alerts) and external providers (e.g., authentication, storage). It simplifies data access and integration across projects.

132 lines (131 loc) 5.25 kB
/* * */ import { roles, sessions, users, verificationTokens } from '../../interfaces/index.js'; import { emailProvider } from '../email/email.js'; import { getAppBaseUrl, HttpException, HttpStatus } from '@tmlmobilidade/lib'; import { AsyncSingletonProxy, Dates, generateRandomString, generateRandomToken, getPermission } from '@tmlmobilidade/utils'; import bcrypt from 'bcryptjs'; /* * */ class AuthProvider { static _instance; /** * Return the instance of the AuthProvider. */ static async getInstance() { if (!AuthProvider._instance) { AuthProvider._instance = new AuthProvider(); } return AuthProvider._instance; } /** * Get Permissions * * @param session_token - The session token * @param scope - The scope to check * @param action - The action to check * @returns The permissions that the user has */ async getPermission(session_token, scope, action) { const user = await this.getUser(session_token); const role_list = await roles.findMany({ _id: { $in: user.role_ids } }); let permission; try { permission = getPermission([...role_list.flatMap(role => role.permissions), ...user.permissions], scope, action); } catch (e) { throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR, 'Error getting permissions', { cause: e }); } if (!permission || Object.keys(permission).length === 0) { throw new HttpException(HttpStatus.FORBIDDEN, 'User does not have permission'); } return permission; } /** * Gets a user by their session token * * @param session_token - The session token to look up * @returns The user associated with the session token * @throws {HttpException} * - UNAUTHORIZED if session not found * - UNAUTHORIZED if user not found */ async getUser(session_token) { // TODO: Implement caching with redis const session = await sessions.findOne({ token: session_token }); if (!session) { throw new HttpException(HttpStatus.UNAUTHORIZED, 'Session not found'); } const user = await users.findOne({ _id: session.user_id }); if (!user) { throw new HttpException(HttpStatus.UNAUTHORIZED, 'User not found'); } return user; } /** * Login a user * * @param username - The username of the user * @param password_hash - The password hash of the user, already hashed with bcrypt in client * @returns The newly created session for the logged in user * @throws {HttpException} * - UNAUTHORIZED if user not found or password is incorrect * - INTERNAL_SERVER_ERROR if login fails */ async login(dto) { // TODO: Implement caching with redis const user = await users.findByEmail(dto.email, true); if (!user) { throw new HttpException(HttpStatus.UNAUTHORIZED, 'User not found'); } const password_hash = await bcrypt.compare(dto.password, user.password_hash ?? ''); if (!password_hash) { throw new HttpException(HttpStatus.UNAUTHORIZED, 'Invalid password'); } const session = { _id: generateRandomString(), created_at: Dates.now('utc').unix_timestamp, token: generateRandomToken(), updated_at: Dates.now('utc').unix_timestamp, user_id: user._id.toString(), }; const result = await sessions.insertOne(session); if (!result.acknowledged) { throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR, 'Error logging in user'); } return session; } /** * Logout a user * * @param session_token - The session token to logout */ async logout(session_token) { // TODO: Invalidate cache await sessions.deleteOne({ token: session_token }); } async register(createUserDto) { // Generate a verification token const verification_token = generateRandomToken(); // Create user without password const userToCreate = { ...createUserDto, }; const result = await users.insertOne(userToCreate); const verification_token_result = await verificationTokens.insertOne({ expires_at: Dates.now('utc').plus({ days: 7 }).unix_timestamp, token: verification_token, user_id: result.insertedId.toString(), }); if (!result.acknowledged) { throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR, 'Error creating user'); } if (!verification_token_result.acknowledged) { throw new HttpException(HttpStatus.INTERNAL_SERVER_ERROR, 'Error creating verification token'); } emailProvider.send({ html: `<p>Click the link below to verify your email: <a target="_blank" href="${getAppBaseUrl('auth')}/verification?token=${verification_token}">Verify Email</a></p>`, subject: 'Verify your email', to: createUserDto.email, }); } } export const authProvider = AsyncSingletonProxy(AuthProvider);