@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
JavaScript
/* * */
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);