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.

166 lines (165 loc) • 7.45 kB
/* * */ import { organizations, roles, sessions, users, verificationTokens } from '../../interfaces/index.js'; import { HTTP_STATUS, HttpException } from '@tmlmobilidade/consts'; import { Dates } from '@tmlmobilidade/dates'; import { generateRandomString, generateRandomToken } from '@tmlmobilidade/strings'; import { asyncSingletonProxy, mergeObjects } from '@tmlmobilidade/utils'; import bcrypt from 'bcryptjs'; /* * */ export const AUTH_SESSION_COOKIE_NAME = 'session_token'; /* * */ class AuthProvider { static _instance; /** * Return the instance of the AuthProvider. */ static async getInstance() { if (!AuthProvider._instance) { AuthProvider._instance = new AuthProvider(); } return AuthProvider._instance; } /** * Get the organization for a user based on their session token. * @param sessionToken The session token to look up. * @returns The user associated with the session token. * @throws An HTTP UNAUTHORIZED error code if user or session not found */ async getOrganizationFromSessionToken(sessionToken) { // Get the user associated with the session token const userData = await this.getUserFromSessionToken(sessionToken); // Find the organization associated with the user const organizationData = await organizations.findOne({ _id: { $eq: userData.organization_id } }); if (!organizationData) return undefined; // Return the user data to the caller return organizationData; } /** * Get Permissions for a user based on their session token. * @param sessionToken The session token. * @returns The permissions that the user has. */ async getPermissionsFromSessionToken(sessionToken) { // Get the user associated with the session token const userData = await this.getUserFromSessionToken(sessionToken); // Return the permissions for the user ID return this.getPermissionsFromUserId(userData._id); } /** * Get Permissions for a user based on their user ID. * @param userId The user ID (optional if sessionToken is provided). * @returns The permissions that the user has. */ async getPermissionsFromUserId(userId) { // Get the user associated with the session token const userData = await users.findById(userId); if (!userData) throw new HttpException(HTTP_STATUS.UNAUTHORIZED, 'User not found.'); // Get the roles assigned to the user const rolesData = await roles.findMany({ _id: { $in: userData.role_ids } }); // Combine permissions from roles and user-specific permissions const allPermissions = [...rolesData.flatMap(role => role.permissions), ...userData.permissions]; // Merge permissions with the same scope and action const permissionsMap = new Map(); for (const permission of allPermissions) { // Setup a unique key based on scope and action const key = `${permission.scope}:${permission.action}`; // If the permission already exists, merge them if (permissionsMap.has(key)) { // Get the existing permission const existingPermission = permissionsMap.get(key); // Merge the existing permission with the new one permissionsMap.set(key, mergeObjects(existingPermission, permission)); } else { // Otherwise, just add the new permission permissionsMap.set(key, permission); } } // Return the merged permissions as an array return Array.from(permissionsMap.values()); } /** * Get a user object from their session token. * @param sessionToken The session token to look up. * @returns The user associated with the session token. * @throws An HTTP UNAUTHORIZED error code if user or session not found */ async getUserFromSessionToken(sessionToken) { // Find the current session in the database const sessionData = await sessions.findOne({ token: { $eq: sessionToken } }); if (!sessionData) throw new HttpException(HTTP_STATUS.UNAUTHORIZED, 'Session not found'); // Find the user associated with the session const userData = await users.findOne({ _id: { $eq: sessionData.user_id } }); if (!userData) throw new HttpException(HTTP_STATUS.UNAUTHORIZED, 'User not found'); // Sanitize the user data by removing sensitive fields userData.password_hash = undefined; // Return the user data to the caller return userData; } /** * 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 An HTTP error code: * - UNAUTHORIZED if user not found or password is incorrect * - INTERNAL_SERVER_ERROR if login fails */ async login(loginDto) { // Find the user by email const userData = await users.findByEmail(loginDto.email, { includeUnsafeProperties: true }); if (!userData) throw new HttpException(HTTP_STATUS.UNAUTHORIZED, 'User not found'); // Check if the password matches the stored hash const passwordHashMatch = await bcrypt.compare(loginDto.password, userData.password_hash ?? ''); if (!passwordHashMatch) throw new HttpException(HTTP_STATUS.UNAUTHORIZED, 'Invalid password'); // Create a new session object if the password matches const newSession = { _id: generateRandomString(), created_at: Dates.now('utc').unix_timestamp, created_by: 'system', expires_at: Dates.now('utc').plus({ days: 30 }).unix_timestamp, token: generateRandomToken(), updated_at: Dates.now('utc').unix_timestamp, updated_by: 'system', user_id: userData._id.toString(), }; // Insert the new session into the database await sessions.insertOne(newSession); // Return the session to the caller return newSession; } /** * Logout a user by removing their session. * @param sessionToken The session token to logout. */ async logout(sessionToken) { await sessions.deleteOne({ token: { $eq: sessionToken } }); } /** * Register a new user. * @param createUserDto The data to create the user * @throws An HTTP error code: * - INTERNAL_SERVER_ERROR if user creation fails */ async register(createUserDto) { // Insert the new user into the database with the provided data const insertNewUserResult = await users.insertOne(createUserDto); // Generate a random token that will be used to verify the user const verificationToken = generateRandomToken(); // Insert the verification token into the database await verificationTokens.insertOne({ expires_at: Dates.now('utc').plus({ days: 7 }).unix_timestamp, token: verificationToken, user_id: insertNewUserResult._id, }); return verificationToken; } } /* * */ export const authProvider = asyncSingletonProxy(AuthProvider);