import { ResourceAlreadyExistsException } from '@nestjs.pro/common'; import { Injectable, UnauthorizedException } from '@nestjs/common'; import { Organization } from '@tco.ai/models/dist/RBAC/Organization'; import { OrganizationCreate } from '@tco.ai/models/dist/RBAC/OrganizationCreate'; import { User } from '@tco.ai/models/dist/RBAC/User'; import { UserRepository } from '@tco.ai/models/dist/RBAC/UserRepository'; import { UserStatus } from '@tco.ai/models/dist/RBAC/UserStatus'; import * as bcrypt from 'bcrypt'; import { OrganizationsService } from '../Organizations/OrganizationsService'; import { UserLogin } from '@tco.ai/models/dist/RBAC/UserLogin'; import { UserRegister } from '@tco.ai/models/dist/RBAC/UserRegister'; import { Connection } from 'typeorm'; import { SendgridUtil } from '@nestjs.pro/common/dist/mail/SendgridUtil'; import { Random } from '@nestjs.pro/common/dist/utilities/Random'; import { UserToken } from './UserToken'; import { ResourceNotFoundException } from '@nestjs.pro/common/dist/exceptions/ResourceNotFoundException'; import * as jwt from 'jsonwebtoken'; import { RolesService } from '../Roles/RolesService'; @Injectable() export class UsersService { private readonly userRepository: UserRepository; public static getJWT(id: string): UserToken { return { token: jwt.sign({ id: id }, process.env.JWT_TOKEN, { expiresIn: 86400 }) }; } public constructor(private readonly connection: Connection, private readonly organizationsService: OrganizationsService, private readonly rolesService: RolesService) { this.userRepository = connection.getCustomRepository(UserRepository); } /** * Creates a user account only if the email address doesn't exist. * * @param {User} user * * @returns {Promise} * * @throws {ResourceAlreadyExistsException} Throws if email address already exists. */ public async create(user: User): Promise { if (await this.getByEmail(user.email)) { throw new ResourceAlreadyExistsException('user by this email already exists'); } else { return this.userRepository.save(user); } } /** * Returns a user object (typically used for getting a profile via /my). * * @param {string} id Users UUID. * * @returns {Promise} */ public getById(id: string): Promise { return new Promise(async (resolve, reject) => { const result = await this.userRepository.findOne({ where: [ { id } ] }); if (result) { resolve(result); } else { reject(new UnauthorizedException('could not locate user')); } }); } /** * Retrieve a specific user based on the owning organization. * * @param {Organization} organization * @param id * * @returns {Promise>} */ public async getByIdAndPrincipalOrganization(organization: Organization, id: string): Promise { return this.userRepository.findOne({ where: [ { id, organization } ] }); } /** * Retrieve a user object ONLY if the email and password matches. * * Note: This will take the plaintext password and automatically encrypt it! * * @param {string} email * * @returns {Promise} */ public async getByEmail(email: string): Promise { return this.userRepository.findOne({ where: [ { email } ] }); } /** * User login. * * @param {UserLogin} userLogin * * @returns {Promise} */ public async login(userLogin: UserLogin): Promise { const user = await this.getByEmail(userLogin.email); if (user) { if (bcrypt.compareSync(userLogin.password, user.password)) { if (user.status === UserStatus.ACTIVE) { return new Promise((resolve, reject) => { resolve(jwt.sign({ id: user.id }, process.env.JWT_SECRET, { expiresIn: 86400 })); }); } else if (user.status === UserStatus.PENDING_CONFIRMATION) { throw new UnauthorizedException({ status: UserStatus.PENDING_CONFIRMATION }); } } } throw new UnauthorizedException({ status: UserStatus.INVALID_CREDENTIALS }); } /** * Registers a new user by creating both the organization and user. * * @param {UserRegister} userRegister * * @returns {Promise} */ public async register(userRegister: UserRegister, status?: UserStatus): Promise { if (await this.getByEmail(userRegister.email)) { throw new ResourceAlreadyExistsException(); } else { // // First create organization so we can later assignToUser it to the // user record that we'll created. // const _organization: OrganizationCreate = new OrganizationCreate(); _organization.name = `${ userRegister.email }'s team`; let organization: Organization = await this.organizationsService.create(_organization); // // Create user assigning the organization to it. // const _user: User = new User(); _user.status = status || UserStatus.PENDING_CONFIRMATION; _user.organization = organization; _user.email = userRegister.email; _user.password = userRegister.password; _user.confirmToken = Random.getRandomCryptoString(100); // // Save the _user object to the database. // const user = await this.create(_user); // // Assign roles // await this.rolesService.generate(user); // // Send the welcome email with the confirm token. // SendgridUtil.send(user.email, 'support@streaming-platform.com', 'd-d005b648e41b49f296efba040f2c41f0', { link: `https://app.streaming-platform.com/login/confirm/${ user.confirmToken }` }); return user; } } /** * Send a password reset email and token. * * @param {string} email * * @returns {Promise} */ public async resetSend(email: string): Promise { const user = await this.getByEmail(email); if (user) { user.forgotToken = Random.getRandomCryptoString(100); await this.userRepository.save(user); SendgridUtil.send(user.email, 'support@streaming-platform.com', 'd-724b546ab26341549080e0113c2c3400', { subject: 'Reset your password', link: `https://app.streaming-platform.com/login/reset?token=${ user.forgotToken }` }); return true; } else { throw new ResourceNotFoundException('email does not exist'); } } /** * Changes password if token matches. * * @param {string} token * @param {string} password * * @returns {Promise} */ // public async resetSubmit(token: string, password: string): Promise { // // const user = await this.userRepository.findOne({ where: { forgotToken: token } }); // // if (user) { // // user.password = password; // // await this.userRepository.save(user); // // return true; // // } else { // // throw new ResourceNotFoundException('could not locate token'); // // } // // } /** * changing users password * @param user * @param newPassword */ public async changePassword(user: User, newPassword: string): Promise { const userRecord = await this.getByEmail(user.email); userRecord.password = newPassword; return this.userRepository.save(userRecord); } public async deleteById(id: string): Promise { return (await this.userRepository.delete({ id })).affected > 0; } public async confirm(confirmToken: string): Promise { const user = await this.userRepository.findOne({ where: { confirmToken, status: UserStatus.PENDING_CONFIRMATION } }); console.log(user); if (user) { user.status = UserStatus.ACTIVE; await this.userRepository.save(user); console.log(123123); return UsersService.getJWT(user.id); } else { throw new ResourceNotFoundException('could not locate confirmation token'); } } }