Skip to content

Commit

Permalink
Merge pull request #7667 from ever-co/fix/email-password-authentication
Browse files Browse the repository at this point in the history
[Fix] Email / Password Authentication
  • Loading branch information
rahul-rocket authored Mar 8, 2024
2 parents 2ecb746 + 61e4486 commit 3bc7ad0
Show file tree
Hide file tree
Showing 2 changed files with 149 additions and 90 deletions.
23 changes: 17 additions & 6 deletions packages/core/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { ApiTags, ApiOperation, ApiResponse, ApiOkResponse, ApiBadRequestRespons
import { CommandBus } from '@nestjs/cqrs';
import { I18nLang } from 'nestjs-i18n';
import { IAuthResponse, IUserSigninWorkspaceResponse, LanguagesEnum } from '@gauzy/contracts';
import { Public, parseToBoolean } from '@gauzy/common';
import { Public } from '@gauzy/common';
import { AuthService } from './auth.service';
import { User as IUser } from '../user/user.entity';
import {
Expand All @@ -26,6 +26,7 @@ import {
WorkspaceSigninVerifyTokenCommand
} from './commands';
import { RequestContext } from '../core/context';
import { convertNativeParameters } from '../core/crud/pagination.helper';
import { AuthRefreshGuard } from './../shared/guards';
import { ChangePasswordRequestDTO, ResetPasswordRequestDTO } from './../password-reset/dto';
import { RegisterUserDTO, UserEmailDTO, UserLoginDTO, UserSigninWorkspaceDTO } from './../user/dto';
Expand Down Expand Up @@ -138,8 +139,12 @@ export class AuthController {
@Post('/login')
@Public()
@UsePipes(new ValidationPipe({ transform: true }))
async login(@Body() input: UserLoginDTO): Promise<IAuthResponse | null> {
return await this.commandBus.execute(new AuthLoginCommand(input));
async login(
@Body() input: UserLoginDTO
): Promise<IAuthResponse | null> {
return await this.commandBus.execute(
new AuthLoginCommand(input)
);
}

/**
Expand All @@ -152,8 +157,14 @@ export class AuthController {
@Post('/signin.email.password')
@Public()
@UsePipes(new ValidationPipe())
async signinWorkspacesByPassword(@Body() input: UserSigninWorkspaceDTO): Promise<IUserSigninWorkspaceResponse> {
return await this.authService.signinWorkspacesByEmailPassword(input);
async signinWorkspacesByPassword(
@Query() query: Record<string, boolean>,
@Body() input: UserSigninWorkspaceDTO
): Promise<IUserSigninWorkspaceResponse> {
return await this.authService.signinWorkspacesByEmailPassword(
input,
convertNativeParameters(query.includeTeams)
);
}

/**
Expand Down Expand Up @@ -191,7 +202,7 @@ export class AuthController {
): Promise<IUserSigninWorkspaceResponse> {
return await this.authService.confirmWorkspaceSigninByCode(
input,
parseToBoolean(query.includeTeams)
convertNativeParameters(query.includeTeams)
);
}

Expand Down
216 changes: 132 additions & 84 deletions packages/core/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,27 @@ export class AuthService extends SocialAuthService {
async login({ email, password }: IUserLoginInput): Promise<IAuthResponse | null> {
try {
const user = await this.userService.findOneByOptions({
where: {
email,
isActive: true,
isArchived: false
},
where: [
{
email,
isActive: true,
isArchived: false,
hash: Not(IsNull()),
employee: {
id: IsNull()
}
},
{
email,
isActive: true,
isArchived: false,
hash: Not(IsNull()),
employee: {
isActive: true, // If employees are inactive
isArchived: false
}
}
],
relations: {
employee: true,
role: true
Expand All @@ -87,10 +103,7 @@ export class AuthService extends SocialAuthService {
createdAt: 'DESC'
}
});
// If employees are inactive
if (isNotEmpty(user.employee) && user.employee.isActive === false) {
throw new UnauthorizedException();
}

// If password is not matching with any user
if (!(await bcrypt.compare(password, user.hash))) {
throw new UnauthorizedException();
Expand Down Expand Up @@ -120,10 +133,11 @@ export class AuthService extends SocialAuthService {
* @returns A promise that resolves to a response with user workspaces.
* @throws UnauthorizedException if authentication fails.
*/
async signinWorkspacesByEmailPassword({
email,
password
}: IUserWorkspaceSigninInput): Promise<IUserSigninWorkspaceResponse> {
async signinWorkspacesByEmailPassword(
input: IUserWorkspaceSigninInput,
includeTeams: boolean
): Promise<IUserSigninWorkspaceResponse> {
const { email, password } = input;
/** Fetching users matching the query */
let users = await this.userService.find({
where: [
Expand Down Expand Up @@ -178,31 +192,15 @@ export class AuthService extends SocialAuthService {
codeExpireAt
});

// Create an array of user objects with relevant data
const workspaces: IWorkspaceResponse[] = users.map((user: IUser) => ({
user: new User({
id: user.id,
email: user.email || null,
name: user.name,
imageUrl: user.imageUrl,
tenant: new Tenant({
id: user.tenant ? user.tenantId : null,
name: user.tenant?.name || '',
logo: user.tenant?.logo || ''
})
}),
token: this.generateToken(user, code)
}));

// Determining the response based on the number of matching users
const response: IUserSigninWorkspaceResponse = {
workspaces: workspaces,
confirmed_email: email,
show_popup: workspaces.length > 1,
total_workspaces: workspaces.length
};
const response: IUserSigninWorkspaceResponse = await this.createUserSigninWorkspaceResponse({
users,
code,
email,
includeTeams
});

if (workspaces.length > 0) {
if (response.total_workspaces > 0) {
return response;
} else {
console.log('Error while signin workspace: %s');
Expand Down Expand Up @@ -714,7 +712,7 @@ export class AuthService extends SocialAuthService {

// Check the value of the 'email' variable against certain demo email addresses
if (email === demoEmployeeEmail || email === demoAdminEmail) {
magicCode = environment.demoCredentialConfig?.employeePassword || '123456';
magicCode = environment.demoCredentialConfig?.employeePassword || DEMO_PASSWORD_LESS_MAGIC_CODE;
isDemoCode = true;
}
}
Expand Down Expand Up @@ -803,56 +801,16 @@ export class AuthService extends SocialAuthService {
}
});

// Create an array of user objects with relevant data
const workspaces: IWorkspaceResponse[] = [];

// Create an array of user objects with relevant data
for await (const user of users) {
const userId = user.id;
const tenantId = user.tenant ? user.tenantId : null;
const employeeId = user.employee ? user.employeeId : null;

const workspace: IWorkspaceResponse = {
user: new User({
id: user.id,
email: user.email || null,
name: user.name || null,
imageUrl: user.imageUrl || null,
tenant: new Tenant({
id: user.tenant ? user.tenantId : null,
name: user.tenant?.name || null,
logo: user.tenant?.logo || null
})
}),
token: this.generateToken(user, code)
};

try {
if (includeTeams) {
console.time('Get teams for a user within a specific tenant');

const teams = await this.getTeamsForUser(tenantId, userId, employeeId);
workspace['current_teams'] = teams;

console.timeEnd('Get teams for a user within a specific tenant');
}
} catch (error) {
console.log('Error while getting specific teams for specific tenant: %s', error?.message);
}

workspaces.push(workspace);
}

// Determining the response based on the number of matching users
const response: IUserSigninWorkspaceResponse = {
workspaces,
confirmed_email: email,
show_popup: workspaces.length > 1,
total_workspaces: workspaces.length
};
const response: IUserSigninWorkspaceResponse = await this.createUserSigninWorkspaceResponse({
users,
code,
email,
includeTeams
});

// Return the response if there are matching users
if (workspaces.length > 0) {
if (response.total_workspaces > 0) {
return response;
}
throw new UnauthorizedException();
Expand Down Expand Up @@ -1030,4 +988,94 @@ export class AuthService extends SocialAuthService {

return await query.getRawMany();
}

/**
* Creates workspace response objects for a list of users.
*
* @param {Object} params - The parameters.
* @param {IUser[]} params.users - The list of users.
* @param {string} params.email - The email address.
* @param {string} params.code - The code for workspace signin.
* @param {boolean} params.includeTeams - Flag to include teams in the response.
* @returns {Promise<IUserSigninWorkspaceResponse>} A promise that resolves to the workspace response.
*/
private async createUserSigninWorkspaceResponse({
users,
email,
code,
includeTeams
}: {
users: IUser[],
email: string,
code: string,
includeTeams: boolean
}): Promise<IUserSigninWorkspaceResponse> {
const workspaces: IWorkspaceResponse[] = [];

for (const user of users) {
const workspace = await this.createWorkspace(user, code, includeTeams);
workspaces.push(workspace);
}

return {
workspaces,
confirmed_email: email,
show_popup: workspaces.length > 1,
total_workspaces: workspaces.length
};
}

/**
* Creates a workspace response object for a given user.
*
* @param user The user object of type IUser.
* @param code The code used for generating the user token.
* @param includeTeams Flag indicating whether to include team information in the response.
* @returns A promise that resolves to the workspace response object of type IWorkspaceResponse.
*/
private async createWorkspace(user: IUser, code: string, includeTeams: boolean): Promise<IWorkspaceResponse> {
const tenantId = user.tenant ? user.tenantId : null;
const employeeId = user.employee ? user.employeeId : null;

const workspace: IWorkspaceResponse = {
user: this.createUserObject(user),
token: this.generateToken(user, code)
};

if (includeTeams) {
try {
console.time('Get teams for a user within a specific tenant');

const teams = await this.getTeamsForUser(tenantId, user.id, employeeId);
workspace['current_teams'] = teams;

console.timeEnd('Get teams for a user within a specific tenant');
} catch (error) {
console.error('Error while getting specific teams for specific tenant:', error?.message);
// Optionally, you might want to handle the error more explicitly here.
}
}

return workspace;
}

/**
* Creates a new User object from a given IUser object.
*
* @param user The IUser object to be transformed.
* @returns A new User object with properties mapped from the IUser object.
*/
private createUserObject(user: IUser): User {
return new User({
id: user.id,
email: user.email || null, // Sets email to null if it's undefined
name: user.name || null, // Sets name to null if it's undefined
imageUrl: user.imageUrl || null, // Sets imageUrl to null if it's undefined
tenant: user.tenant ? new Tenant({
id: user.tenant.id, // Assuming tenantId is a direct property of tenant
name: user.tenant.name || '', // Defaulting to an empty string if name is undefined
logo: user.tenant.logo || '' // Defaulting to an empty string if logo is undefined
}) : null // Sets tenant to null if user.tenant is undefined
});
}
}

0 comments on commit 3bc7ad0

Please sign in to comment.