2022-05-05 07:08:42 +00:00
|
|
|
import { Injectable, NotAcceptableException, NotFoundException, UnauthorizedException } from '@nestjs/common';
|
2021-07-11 05:39:55 +00:00
|
|
|
import { UsersService } from './users.service';
|
2021-07-19 05:42:16 +00:00
|
|
|
import { OrganizationsService } from './organizations.service';
|
2021-07-08 07:39:07 +00:00
|
|
|
import { JwtService } from '@nestjs/jwt';
|
2021-07-11 05:13:51 +00:00
|
|
|
import { User } from '../entities/user.entity';
|
2021-07-19 05:42:16 +00:00
|
|
|
import { OrganizationUsersService } from './organization_users.service';
|
2021-07-26 14:30:12 +00:00
|
|
|
import { EmailService } from './email.service';
|
2021-10-25 08:35:32 +00:00
|
|
|
import { decamelizeKeys } from 'humps';
|
2022-05-05 07:08:42 +00:00
|
|
|
import { Organization } from 'src/entities/organization.entity';
|
|
|
|
|
import { ConfigService } from '@nestjs/config';
|
|
|
|
|
import { SSOConfigs } from 'src/entities/sso_config.entity';
|
2021-07-08 07:39:07 +00:00
|
|
|
const bcrypt = require('bcrypt');
|
2021-09-21 13:48:28 +00:00
|
|
|
const uuid = require('uuid');
|
2021-07-08 07:39:07 +00:00
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
|
export class AuthService {
|
|
|
|
|
constructor(
|
|
|
|
|
private usersService: UsersService,
|
2021-07-19 05:42:16 +00:00
|
|
|
private jwtService: JwtService,
|
|
|
|
|
private organizationsService: OrganizationsService,
|
2021-07-26 14:30:12 +00:00
|
|
|
private organizationUsersService: OrganizationUsersService,
|
2022-05-05 07:08:42 +00:00
|
|
|
private emailService: EmailService,
|
|
|
|
|
private configService: ConfigService
|
2021-09-21 13:48:28 +00:00
|
|
|
) {}
|
2021-07-08 07:39:07 +00:00
|
|
|
|
2021-11-15 15:45:16 +00:00
|
|
|
verifyToken(token: string) {
|
|
|
|
|
try {
|
|
|
|
|
const signedJwt = this.jwtService.verify(token);
|
|
|
|
|
return signedJwt;
|
|
|
|
|
} catch (err) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-05 07:08:42 +00:00
|
|
|
private async validateUser(email: string, password: string, organisationId?: string): Promise<User> {
|
|
|
|
|
const user = await this.usersService.findByEmail(email, organisationId);
|
|
|
|
|
|
2021-09-21 13:48:28 +00:00
|
|
|
if (!user) return null;
|
2021-07-10 07:01:13 +00:00
|
|
|
|
|
|
|
|
const isVerified = await bcrypt.compare(password, user.password);
|
2021-07-08 07:39:07 +00:00
|
|
|
|
|
|
|
|
return isVerified ? user : null;
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-05 07:08:42 +00:00
|
|
|
async login(email: string, password: string, organizationId?: string) {
|
|
|
|
|
let organization: Organization;
|
|
|
|
|
|
|
|
|
|
const user = await this.validateUser(email, password, organizationId);
|
2021-07-08 07:39:07 +00:00
|
|
|
|
2022-01-07 09:16:23 +00:00
|
|
|
if (user && (await this.usersService.status(user)) !== 'archived') {
|
2022-05-05 07:08:42 +00:00
|
|
|
if (!organizationId) {
|
|
|
|
|
// Global login
|
|
|
|
|
// Determine the organization to be loaded
|
2022-05-11 11:00:25 +00:00
|
|
|
if (this.configService.get<string>('DISABLE_MULTI_WORKSPACE') === 'true') {
|
2022-05-05 07:08:42 +00:00
|
|
|
// Single organization
|
|
|
|
|
organization = await this.organizationsService.getSingleOrganization();
|
|
|
|
|
if (!organization?.ssoConfigs?.find((oc) => oc.sso == 'form' && oc.enabled)) {
|
|
|
|
|
throw new UnauthorizedException();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
const organizationList: Organization[] = await this.organizationsService.findOrganizationSupportsFormLogin(
|
|
|
|
|
user
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const defaultOrgDetails: Organization = organizationList?.find((og) => og.id === user.defaultOrganizationId);
|
|
|
|
|
// Multi organization
|
|
|
|
|
if (defaultOrgDetails) {
|
|
|
|
|
// default organization form login enabled
|
|
|
|
|
organization = defaultOrgDetails;
|
|
|
|
|
} else if (organizationList?.length > 0) {
|
|
|
|
|
// default organization form login not enabled, picking first one from form enabled list
|
|
|
|
|
organization = organizationList[0];
|
|
|
|
|
} else {
|
|
|
|
|
// no form login enabled organization available for user - creating new one
|
2022-05-11 11:00:25 +00:00
|
|
|
organization = await this.organizationsService.create('Untitled workspace', user);
|
2022-05-05 07:08:42 +00:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
user.organizationId = organization.id;
|
|
|
|
|
} else {
|
|
|
|
|
// organization specific login
|
|
|
|
|
user.organizationId = organizationId;
|
|
|
|
|
|
|
|
|
|
organization = await this.organizationsService.get(user.organizationId);
|
|
|
|
|
const formConfigs: SSOConfigs = organization?.ssoConfigs?.find((sso) => sso.sso === 'form');
|
|
|
|
|
|
|
|
|
|
if (!formConfigs?.enabled) {
|
|
|
|
|
// no configurations in organization side or Form login disabled for the organization
|
|
|
|
|
throw new UnauthorizedException('Password login is disabled for the organization');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (user.defaultOrganizationId !== user.organizationId) {
|
|
|
|
|
// Updating default organization Id
|
|
|
|
|
await this.usersService.updateDefaultOrganization(user, organization.id);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const payload = {
|
|
|
|
|
username: user.id,
|
|
|
|
|
sub: user.email,
|
|
|
|
|
organizationId: user.organizationId,
|
|
|
|
|
isPasswordLogin: true,
|
|
|
|
|
};
|
2021-07-08 07:39:07 +00:00
|
|
|
|
2021-10-25 08:35:32 +00:00
|
|
|
return decamelizeKeys({
|
|
|
|
|
id: user.id,
|
2021-07-10 13:54:32 +00:00
|
|
|
auth_token: this.jwtService.sign(payload),
|
2021-08-09 12:10:44 +00:00
|
|
|
email: user.email,
|
|
|
|
|
first_name: user.firstName,
|
2021-09-10 09:40:23 +00:00
|
|
|
last_name: user.lastName,
|
2022-05-05 07:08:42 +00:00
|
|
|
organizationId: user.organizationId,
|
|
|
|
|
organization: organization.name,
|
2021-10-11 15:15:58 +00:00
|
|
|
admin: await this.usersService.hasGroup(user, 'admin'),
|
2021-10-25 08:35:32 +00:00
|
|
|
group_permissions: await this.usersService.groupPermissions(user),
|
|
|
|
|
app_group_permissions: await this.usersService.appGroupPermissions(user),
|
|
|
|
|
});
|
2021-07-08 07:39:07 +00:00
|
|
|
} else {
|
|
|
|
|
throw new UnauthorizedException('Invalid credentials');
|
|
|
|
|
}
|
|
|
|
|
}
|
2021-07-19 05:42:16 +00:00
|
|
|
|
2022-05-05 07:08:42 +00:00
|
|
|
async switchOrganization(newOrganizationId: string, user: User, isNewOrganization?: boolean) {
|
|
|
|
|
if (!(isNewOrganization || user.isPasswordLogin)) {
|
|
|
|
|
throw new UnauthorizedException();
|
|
|
|
|
}
|
2022-05-11 11:00:25 +00:00
|
|
|
if (this.configService.get<string>('DISABLE_MULTI_WORKSPACE') === 'true') {
|
2022-05-05 07:08:42 +00:00
|
|
|
throw new UnauthorizedException();
|
2021-07-19 05:42:16 +00:00
|
|
|
}
|
2022-05-05 07:08:42 +00:00
|
|
|
const newUser = await this.usersService.findByEmail(user.email, newOrganizationId);
|
|
|
|
|
|
|
|
|
|
if (newUser && (await this.usersService.status(newUser)) !== 'archived') {
|
|
|
|
|
newUser.organizationId = newOrganizationId;
|
|
|
|
|
|
|
|
|
|
const organization: Organization = await this.organizationsService.get(newUser.organizationId);
|
|
|
|
|
|
|
|
|
|
const formConfigs: SSOConfigs = organization?.ssoConfigs?.find((sso) => sso.sso === 'form');
|
|
|
|
|
|
|
|
|
|
if (!formConfigs?.enabled) {
|
|
|
|
|
// no configurations in organization side or Form login disabled for the organization
|
|
|
|
|
throw new UnauthorizedException('Password login disabled for the organization');
|
|
|
|
|
}
|
2021-09-21 13:48:28 +00:00
|
|
|
|
2022-05-05 07:08:42 +00:00
|
|
|
// Updating default organization Id
|
|
|
|
|
await this.usersService.updateDefaultOrganization(newUser, newUser.organizationId);
|
|
|
|
|
|
|
|
|
|
const payload = {
|
|
|
|
|
username: user.id,
|
|
|
|
|
sub: user.email,
|
|
|
|
|
organizationId: newUser.organizationId,
|
|
|
|
|
isPasswordLogin: true,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return decamelizeKeys({
|
|
|
|
|
id: newUser.id,
|
|
|
|
|
auth_token: this.jwtService.sign(payload),
|
|
|
|
|
email: newUser.email,
|
|
|
|
|
first_name: newUser.firstName,
|
|
|
|
|
last_name: newUser.lastName,
|
|
|
|
|
organizationId: newUser.organizationId,
|
|
|
|
|
organization: organization.name,
|
|
|
|
|
admin: await this.usersService.hasGroup(newUser, 'admin'),
|
|
|
|
|
group_permissions: await this.usersService.groupPermissions(newUser),
|
|
|
|
|
app_group_permissions: await this.usersService.appGroupPermissions(newUser),
|
|
|
|
|
});
|
|
|
|
|
} else {
|
|
|
|
|
throw new UnauthorizedException('Invalid credentials');
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async signup(email: string) {
|
2022-03-04 02:28:02 +00:00
|
|
|
const existingUser = await this.usersService.findByEmail(email);
|
2022-05-05 07:08:42 +00:00
|
|
|
if (existingUser?.invitationToken || existingUser?.organizationUsers?.some((ou) => ou.status === 'active')) {
|
2022-03-04 02:28:02 +00:00
|
|
|
throw new NotAcceptableException('Email already exists');
|
|
|
|
|
}
|
|
|
|
|
|
2022-05-05 07:08:42 +00:00
|
|
|
let organization: Organization;
|
|
|
|
|
// Check if the configs allows user signups
|
2022-05-11 11:00:25 +00:00
|
|
|
if (this.configService.get<string>('DISABLE_MULTI_WORKSPACE') === 'true') {
|
2022-05-05 07:08:42 +00:00
|
|
|
// Single organization checking if organization exist
|
|
|
|
|
organization = await this.organizationsService.getSingleOrganization();
|
2021-07-19 05:42:16 +00:00
|
|
|
|
2022-05-05 07:08:42 +00:00
|
|
|
if (organization) {
|
|
|
|
|
throw new NotAcceptableException('Multi organization not supported - organization exist');
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// Multi organization
|
|
|
|
|
if (this.configService.get<string>('DISABLE_SIGNUPS') === 'true') {
|
|
|
|
|
throw new NotAcceptableException();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Create default organization
|
2022-05-11 11:00:25 +00:00
|
|
|
organization = await this.organizationsService.create('Untitled workspace');
|
2022-05-05 07:08:42 +00:00
|
|
|
const user = await this.usersService.create({ email }, organization.id, ['all_users', 'admin'], existingUser, true);
|
|
|
|
|
await this.organizationUsersService.create(user, organization, true);
|
2021-12-10 03:13:05 +00:00
|
|
|
await this.emailService.sendWelcomeEmail(user.email, user.firstName, user.invitationToken);
|
2021-07-26 14:30:12 +00:00
|
|
|
|
2022-01-19 09:32:36 +00:00
|
|
|
return {};
|
2021-07-26 16:02:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async forgotPassword(email: string) {
|
|
|
|
|
const user = await this.usersService.findByEmail(email);
|
|
|
|
|
const forgotPasswordToken = uuid.v4();
|
2021-12-10 03:13:05 +00:00
|
|
|
await this.usersService.update(user.id, { forgotPasswordToken });
|
|
|
|
|
await this.emailService.sendPasswordResetEmail(email, forgotPasswordToken);
|
2021-07-26 16:02:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async resetPassword(token: string, password: string) {
|
|
|
|
|
const user = await this.usersService.findByPasswordResetToken(token);
|
2021-09-21 13:48:28 +00:00
|
|
|
if (!user) {
|
|
|
|
|
throw new NotFoundException('Invalid token');
|
2021-07-26 16:02:47 +00:00
|
|
|
} else {
|
2021-12-10 03:13:05 +00:00
|
|
|
await this.usersService.update(user.id, {
|
2021-10-11 15:15:58 +00:00
|
|
|
password,
|
|
|
|
|
forgotPasswordToken: null,
|
|
|
|
|
});
|
2021-07-26 16:02:47 +00:00
|
|
|
}
|
2021-07-19 05:42:16 +00:00
|
|
|
}
|
2021-07-08 07:39:07 +00:00
|
|
|
}
|