CVE-2022-2037 (published 2022-06-09T17:15Z)

Excessive Attack Surface in GitHub repository tooljet/tooljet prior to v1.16.0.

Severity: MEDIUM
CVSS v2 base score: 6.0 (AV:N/AC:M/Au:S/C:P/I:P/A:P)
CVSS v3 base score: 8.0, HIGH (CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: NVD-CWE-Other
cwe_name: Other
description: NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
extended_description: Insufficient Information

Fix information:
repo_url: https://github.com/tooljet/tooljet
commit_details: repo_url: https://github.com/tooljet/tooljet
author: Midhun G S
author_date: 2022-06-02 15:20:51+05:30
msg: [BUG] Account level security issue (#3150)

* bug fixes

* changes

* changes for single workspace support

* added guards for signup route

* test cases fixes

* Workspace invite and user onboarding flow changes (#3190)

* invite user flow changes

* review comments

* cleanup

* testcase fix
repository: repo_url: https://github.com/tooljet/tooljet
repo_name: ToolJet/ToolJet
description: Low-code platform for building business applications. Connect to databases, cloud storages, GraphQL, API endpoints, Airtable, Google sheets, OpenAI, etc and build apps using drag and drop application builder. Built using JavaScript/TypeScript. 🚀
repo_language: JavaScript
owner: tooljet
file_changes: filename: App.jsx
change_type: ModificationType.MODIFY
diff: @@ -142,6 +142,20 @@ class App extends React.Component {
                 />
               )}
             />
+            <Route
+              path="/invitations/:token/workspaces/:organizationToken"
+              render={(props) => (
+                <Redirect
+                  to={{
+                    pathname: '/confirm',
+                    state: {
+                      token: props.match.params.token,
+                      organizationToken: props.match.params.organizationToken,
+                    },
+                  }}
+                />
+              )}
+            />
             <Route path="/confirm" component={ConfirmationPage} />
             <Route
               path="/organization-invitations/:token"

programming_language: TypeScript
filename: ConfirmationPage.jsx
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,5 @@
 import React from 'react';
-import { userService } from '@/_services';
+import { appService } from '@/_services';
 import { toast } from 'react-hot-toast';
 
 class ConfirmationPage extends React.Component {
@@ -23,7 +23,7 @@ class ConfirmationPage extends React.Component {
 
   setPassword = (e) => {
     e.preventDefault();
-    const token = this.props.location.state.token;
+    const { token, organizationToken } = this.props.location.state;
     const { password, organization, role, firstName, lastName, password_confirmation } = this.state;
     this.setState({ isLoading: true });
 
@@ -43,9 +43,10 @@ class ConfirmationPage extends React.Component {
       return;
     }
 
-    userService
+    appService
       .setPasswordFromToken({
         token,
+        organizationToken,
         password,
         organization,
         role,

programming_language: TypeScript
filename: OrganizationInvitationPage.jsx
change_type: ModificationType.MODIFY
diff: @@ -1,5 +1,5 @@
 import React from 'react';
-import { userService } from '@/_services';
+import { appService } from '@/_services';
 import { toast } from 'react-hot-toast';
 
 class OrganizationInvitationPage extends React.Component {
@@ -42,21 +42,22 @@ class OrganizationInvitationPage extends React.Component {
       }
     }
 
-    userService
+    appService
       .acceptInvite({
         token,
         password,
       })
-      .then(() => {
+      .then((response) => {
         this.setState({ isLoading: false });
-        toast.success(`Added to the workspace${isSetPassword ? ' and password has been set ' : ' '}successfully.`, {
-          position: 'top-center',
+        response.json().then((data) => {
+          if (!response.ok) {
+            return toast.error(data?.message || 'Error while setting up your account.', { position: 'top-center' });
+          }
+          toast.success(`Added to the workspace${isSetPassword ? ' and password has been set ' : ' '}successfully.`, {
+            position: 'top-center',
+          });
+          this.props.history.push('/login');
         });
-        this.props.history.push('/login');
-      })
-      .catch(({ error }) => {
-        this.setState({ isLoading: false });
-        toast.error(error, { position: 'top-center' });
       });
   };
 
@@ -73,7 +74,7 @@ class OrganizationInvitationPage extends React.Component {
           </div>
           <form className="card card-md" action="." method="get" autoComplete="off">
             <div className="card-body">
-              {!this.single_organization && (
+              {!this.single_organization ? (
                 <>
                   <h2 className="card-title text-center mb-2">Already have an account?</h2>
                   <div className="mb-3">
@@ -85,53 +86,51 @@ class OrganizationInvitationPage extends React.Component {
                       Accept invite
                     </button>
                   </div>
-                  <div className="org-invite-or">
-                    <h2>
-                      <span>OR</span>
-                    </h2>
+                </>
+              ) : (
+                <>
+                  <h2 className="card-title text-center mb-4">Set up your account</h2>
+                  <div className="mb-3">
+                    <label className="form-label">Password</label>
+                    <div className="input-group input-group-flat">
+                      <input
+                        onChange={this.handleChange}
+                        name="password"
+                        type="password"
+                        className="form-control"
+                        autoComplete="off"
+                      />
+                      <span className="input-group-text"></span>
+                    </div>
+                  </div>
+                  <div className="mb-3">
+                    <label className="form-label">Confirm Password</label>
+                    <div className="input-group input-group-flat">
+                      <input
+                        onChange={this.handleChange}
+                        name="password_confirmation"
+                        type="password"
+                        className="form-control"
+                        autoComplete="off"
+                      />
+                      <span className="input-group-text"></span>
+                    </div>
+                  </div>
+                  <div className="form-footer">
+                    <p>
+                      By clicking the button below, you agree to our{' '}
+                      <a href="https://tooljet.io/terms">Terms and Conditions</a>.
+                    </p>
+                    <button
+                      className={`btn mt-2 btn-primary w-100 ${isLoading ? ' btn-loading' : ''}`}
+                      onClick={(e) => this.acceptInvite(e, true)}
+                      disabled={isLoading}
+                    >
+                      Finish account setup and accept invite
+                    </button>
                   </div>
                 </>
               )}
-              <h2 className="card-title text-center mb-4">Set up your account</h2>
-              <div className="mb-3">
-                <label className="form-label">Password</label>
-                <div className="input-group input-group-flat">
-                  <input
-                    onChange={this.handleChange}
-                    name="password"
-                    type="password"
-                    className="form-control"
-                    autoComplete="off"
-                  />
-                  <span className="input-group-text"></span>
-                </div>
-              </div>
-              <div className="mb-3">
-                <label className="form-label">Confirm Password</label>
-                <div className="input-group input-group-flat">
-                  <input
-                    onChange={this.handleChange}
-                    name="password_confirmation"
-                    type="password"
-                    className="form-control"
-                    autoComplete="off"
-                  />
-                  <span className="input-group-text"></span>
-                </div>
-              </div>
-              <div className="form-footer">
-                <p>
-                  By clicking the button below, you agree to our{' '}
-                  <a href="https://tooljet.io/terms">Terms and Conditions</a>.
-                </p>
-                <button
-                  className={`btn mt-2 btn-primary w-100 ${isLoading ? ' btn-loading' : ''}`}
-                  onClick={(e) => this.acceptInvite(e, true)}
-                  disabled={isLoading}
-                >
-                  Finish account setup and accept invite
-                </button>
-              </div>
             </div>
           </form>
         </div>

programming_language: TypeScript
filename: ManageOrgUsers.jsx
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,6 @@ import React from 'react';
 import { authenticationService, organizationService, organizationUserService } from '@/_services';
 import { Header } from '@/_components';
 import { toast } from 'react-hot-toast';
-import { history } from '@/_helpers';
 import { CopyToClipboard } from 'react-copy-to-clipboard';
 import ReactTooltip from 'react-tooltip';
 
@@ -26,7 +25,6 @@ class ManageOrgUsers extends React.Component {
   }
 
   validateEmail(email) {
-    console.log(email);
     const re =
       /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
     return re.test(String(email).toLowerCase());
@@ -168,11 +166,6 @@ class ManageOrgUsers extends React.Component {
     }
   };
 
-  logout = () => {
-    authenticationService.logout();
-    history.push('/login');
-  };
-
   generateInvitationURL = (user) => window.location.origin + '/organization-invitations/' + user.invitation_token;
 
   invitationLinkCopyHandler = () => {

programming_language: TypeScript
filename: app.service.js
change_type: ModificationType.MODIFY
diff: @@ -19,6 +19,8 @@ export const appService = {
   setVisibility,
   setMaintenance,
   setSlug,
+  setPasswordFromToken,
+  acceptInvite,
 };
 
 function getConfig() {
@@ -126,3 +128,28 @@ function setSlug(appId, slug) {
   const requestOptions = { method: 'PUT', headers: authHeader(), body: JSON.stringify({ app: { slug: slug } }) };
   return fetch(`${config.apiUrl}/apps/${appId}`, requestOptions).then(handleResponse);
 }
+
+function setPasswordFromToken({ token, password, organization, role, firstName, lastName, organizationToken }) {
+  const body = {
+    token,
+    organizationToken,
+    password,
+    organization,
+    role,
+    first_name: firstName,
+    last_name: lastName,
+  };
+
+  const requestOptions = { method: 'POST', headers: authHeader(), body: JSON.stringify(body) };
+  return fetch(`${config.apiUrl}/set-password-from-token`, requestOptions).then(handleResponse);
+}
+
+function acceptInvite({ token, password }) {
+  const body = {
+    token,
+    password,
+  };
+
+  const requestOptions = { method: 'POST', headers: authHeader(), body: JSON.stringify(body) };
+  return fetch(`${config.apiUrl}/accept-invite`, requestOptions);
+}

programming_language: TypeScript
filename: user.service.js
change_type: ModificationType.MODIFY
diff: @@ -5,10 +5,8 @@ export const userService = {
   getAll,
   createUser,
   deleteUser,
-  setPasswordFromToken,
   updateCurrentUser,
   changePassword,
-  acceptInvite,
   getAvatar,
   updateAvatar,
 };
@@ -53,30 +51,6 @@ function deleteUser(id) {
   return fetch(`${config.apiUrl}/users/${id}`, requestOptions).then(handleResponse);
 }
 
-function setPasswordFromToken({ token, password, organization, role, firstName, lastName }) {
-  const body = {
-    token,
-    password,
-    organization,
-    role,
-    first_name: firstName,
-    last_name: lastName,
-  };
-
-  const requestOptions = { method: 'POST', headers: authHeader(), body: JSON.stringify(body) };
-  return fetch(`${config.apiUrl}/users/set_password_from_token`, requestOptions).then(handleResponse);
-}
-
-function acceptInvite({ token, password }) {
-  const body = {
-    token,
-    password,
-  };
-
-  const requestOptions = { method: 'POST', headers: authHeader(), body: JSON.stringify(body) };
-  return fetch(`${config.apiUrl}/users/accept-invite`, requestOptions).then(handleResponse);
-}
-
 function updateCurrentUser(firstName, lastName) {
   const body = { first_name: firstName, last_name: lastName };
   const requestOptions = { method: 'PATCH', headers: authHeader(), body: JSON.stringify(body) };

programming_language: TypeScript
filename: app.controller.ts
change_type: ModificationType.MODIFY
diff: @@ -3,6 +3,10 @@ import { User } from 'src/decorators/user.decorator';
 import { JwtAuthGuard } from '../../src/modules/auth/jwt-auth.guard';
 import { AppAuthenticationDto, AppForgotPasswordDto, AppPasswordResetDto } from '@dto/app-authentication.dto';
 import { AuthService } from '../services/auth.service';
+import { MultiOrganizationGuard } from 'src/modules/auth/multi-organization.guard';
+import { SignupDisableGuard } from 'src/modules/auth/signup-disable.guard';
+import { CreateUserDto } from '@dto/user.dto';
+import { AcceptInviteDto } from '@dto/accept-organization-invite.dto';
 
 @Controller()
 export class AppController {
@@ -22,6 +26,20 @@ export class AppController {
     return await this.authService.switchOrganization(organizationId, user);
   }
 
+  @UseGuards(MultiOrganizationGuard, SignupDisableGuard)
+  @Post('set-password-from-token')
+  async create(@Body() userCreateDto: CreateUserDto) {
+    await this.authService.setupAccountFromInvitationToken(userCreateDto);
+    return {};
+  }
+
+  @Post('accept-invite')
+  async acceptInvite(@Body() acceptInviteDto: AcceptInviteDto) {
+    await this.authService.acceptOrganizationInvite(acceptInviteDto);
+    return {};
+  }
+
+  @UseGuards(MultiOrganizationGuard, SignupDisableGuard)
   @Post('signup')
   async signup(@Body() appAuthDto: AppAuthenticationDto) {
     return this.authService.signup(appAuthDto.email);

programming_language: TypeScript
filename: organization_users.controller.ts
change_type: ModificationType.MODIFY
diff: @@ -8,17 +8,21 @@ import { CheckPolicies } from 'src/modules/casl/check_policies.decorator';
 import { User as UserEntity } from 'src/entities/user.entity';
 import { User } from 'src/decorators/user.decorator';
 import { InviteNewUserDto } from '../dto/invite-new-user.dto';
+import { OrganizationsService } from '@services/organizations.service';
 
 @Controller('organization_users')
 export class OrganizationUsersController {
-  constructor(private organizationUsersService: OrganizationUsersService) {}
+  constructor(
+    private organizationUsersService: OrganizationUsersService,
+    private organizationsService: OrganizationsService
+  ) {}
 
   // Endpoint for inviting new organization users
   @UseGuards(JwtAuthGuard, PoliciesGuard)
   @CheckPolicies((ability: AppAbility) => ability.can('inviteUser', UserEntity))
   @Post()
   async create(@User() user, @Body() inviteNewUserDto: InviteNewUserDto) {
-    const result = await this.organizationUsersService.inviteNewUser(user, inviteNewUserDto);
+    const result = await this.organizationsService.inviteNewUser(user, inviteNewUserDto);
     return decamelizeKeys({ users: result });
   }
 

programming_language: TypeScript
filename: users.controller.ts
change_type: ModificationType.MODIFY
diff: @@ -1,32 +1,16 @@
-import { Body, Controller, Post, Patch, UseGuards, UseInterceptors, Req, UploadedFile } from '@nestjs/common';
+import { Body, Controller, Post, Patch, UseGuards, UseInterceptors, UploadedFile } from '@nestjs/common';
 import { Express } from 'express';
 import { FileInterceptor } from '@nestjs/platform-express';
 import { JwtAuthGuard } from 'src/modules/auth/jwt-auth.guard';
 import { PasswordRevalidateGuard } from 'src/modules/auth/password-revalidate.guard';
 import { UsersService } from 'src/services/users.service';
 import { User } from 'src/decorators/user.decorator';
-import { SignupDisableGuard } from 'src/modules/auth/signup-disable.guard';
-import { CreateUserDto, UpdateUserDto } from '@dto/user.dto';
-import { AcceptInviteDto } from '@dto/accept-organization-invite.dto';
-import { MultiOrganizationGuard } from 'src/modules/auth/multi-organization.guard';
+import { UpdateUserDto } from '@dto/user.dto';
 
 @Controller('users')
 export class UsersController {
   constructor(private usersService: UsersService) {}
 
-  @UseGuards(MultiOrganizationGuard, SignupDisableGuard)
-  @Post('set_password_from_token')
-  async create(@Body() userCreateDto: CreateUserDto) {
-    await this.usersService.setupAccountFromInvitationToken(userCreateDto);
-    return {};
-  }
-
-  @Post('accept-invite')
-  async acceptInvite(@Body() acceptInviteDto: AcceptInviteDto) {
-    await this.usersService.acceptOrganizationInvite(acceptInviteDto);
-    return {};
-  }
-
   @UseGuards(JwtAuthGuard)
   @Patch('update')
   async update(@User() user, @Body() updateUserDto: UpdateUserDto) {
@@ -42,8 +26,8 @@ export class UsersController {
   @Post('avatar')
   @UseGuards(JwtAuthGuard)
   @UseInterceptors(FileInterceptor('file'))
-  async addAvatar(@Req() req, @UploadedFile() file: Express.Multer.File) {
-    return this.usersService.addAvatar(req.user.id, file.buffer, file.originalname);
+  async addAvatar(@User() user, @UploadedFile() file: Express.Multer.File) {
+    return this.usersService.addAvatar(user.id, file.buffer, file.originalname);
   }
 
   @UseGuards(JwtAuthGuard, PasswordRevalidateGuard)

programming_language: TypeScript
filename: accept-organization-invite.dto.ts
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,4 @@
-import { IsString, IsOptional, IsNotEmpty } from 'class-validator';
+import { IsString, IsNotEmpty, IsOptional } from 'class-validator';
 
 export class AcceptInviteDto {
   @IsString()

programming_language: TypeScript
filename: user.dto.ts
change_type: ModificationType.MODIFY
diff: @@ -26,6 +26,11 @@ export class CreateUserDto {
   @IsNotEmpty()
   password: string;
 
+  @IsString()
+  @IsOptional()
+  @IsNotEmpty()
+  organizationToken: string;
+
   @IsString()
   @IsNotEmpty()
   token: string;

programming_language: TypeScript
filename: auth.service.ts
change_type: ModificationType.MODIFY
diff: @@ -1,4 +1,10 @@
-import { Injectable, NotAcceptableException, NotFoundException, UnauthorizedException } from '@nestjs/common';
+import {
+  BadRequestException,
+  Injectable,
+  NotAcceptableException,
+  NotFoundException,
+  UnauthorizedException,
+} from '@nestjs/common';
 import { UsersService } from './users.service';
 import { OrganizationsService } from './organizations.service';
 import { JwtService } from '@nestjs/jwt';
@@ -9,12 +15,23 @@ import { decamelizeKeys } from 'humps';
 import { Organization } from 'src/entities/organization.entity';
 import { ConfigService } from '@nestjs/config';
 import { SSOConfigs } from 'src/entities/sso_config.entity';
+import { InjectRepository } from '@nestjs/typeorm';
+import { Repository } from 'typeorm';
+import { OrganizationUser } from 'src/entities/organization_user.entity';
+import { CreateUserDto } from '@dto/user.dto';
+import { AcceptInviteDto } from '@dto/accept-organization-invite.dto';
 const bcrypt = require('bcrypt');
 const uuid = require('uuid');
 
 @Injectable()
 export class AuthService {
   constructor(
+    @InjectRepository(User)
+    private usersRepository: Repository<User>,
+    @InjectRepository(OrganizationUser)
+    private organizationUsersRepository: Repository<OrganizationUser>,
+    @InjectRepository(Organization)
+    private organizationsRepository: Repository<Organization>,
     private usersService: UsersService,
     private jwtService: JwtService,
     private organizationsService: OrganizationsService,
@@ -32,8 +49,8 @@ export class AuthService {
     }
   }
 
-  private async validateUser(email: string, password: string, organisationId?: string): Promise<User> {
-    const user = await this.usersService.findByEmail(email, organisationId);
+  private async validateUser(email: string, password: string, organizationId?: string): Promise<User> {
+    const user = await this.usersService.findByEmail(email, organizationId);
 
     if (!user) return null;
 
@@ -169,10 +186,19 @@ export class AuthService {
 
   async signup(email: string) {
     const existingUser = await this.usersService.findByEmail(email);
-    if (existingUser?.invitationToken || existingUser?.organizationUsers?.some((ou) => ou.status === 'active')) {
+    if (existingUser?.organizationUsers?.some((ou) => ou.status === 'active')) {
       throw new NotAcceptableException('Email already exists');
     }
 
+    if (existingUser?.invitationToken) {
+      await this.emailService.sendWelcomeEmail(
+        existingUser.email,
+        existingUser.firstName,
+        existingUser.invitationToken
+      );
+      return;
+    }
+
     let organization: Organization;
     // Check if the configs allows user signups
     if (this.configService.get<string>('DISABLE_MULTI_WORKSPACE') === 'true') {
@@ -215,4 +241,120 @@ export class AuthService {
       });
     }
   }
+
+  async setupAccountFromInvitationToken(userCreateDto: CreateUserDto) {
+    const {
+      organization,
+      password,
+      token,
+      role,
+      first_name: firstName,
+      last_name: lastName,
+      organizationToken,
+    } = userCreateDto;
+
+    if (!token) {
+      throw new BadRequestException('Invalid token');
+    }
+
+    const user: User = await this.usersRepository.findOne({ where: { invitationToken: token } });
+
+    if (!user?.organizationUsers) {
+      throw new BadRequestException('Invalid invitation link');
+    }
+    const organizationUser: OrganizationUser = user.organizationUsers.find(
+      (ou) => ou.organizationId === user.defaultOrganizationId
+    );
+
+    if (!organizationUser) {
+      throw new BadRequestException('Invalid invitation link');
+    }
+
+    await this.usersRepository.save(
+      Object.assign(user, {
+        firstName,
+        lastName,
+        password,
+        role,
+        invitationToken: null,
+      })
+    );
+
+    await this.organizationUsersRepository.save(
+      Object.assign(organizationUser, {
+        invitationToken: null,
+        status: 'active',
+      })
+    );
+
+    if (organization) {
+      await this.organizationsRepository.update(user.defaultOrganizationId, {
+        name: organization,
+      });
+    }
+
+    if (this.configService.get<string>('DISABLE_MULTI_WORKSPACE') !== 'true' && organizationToken) {
+      const organizationUser = await this.organizationUsersRepository.findOne({
+        where: { invitationToken: organizationToken },
+      });
+
+      if (organizationUser) {
+        await this.organizationUsersRepository.save(
+          Object.assign(organizationUser, {
+            invitationToken: null,
+            status: 'active',
+          })
+        );
+      }
+    }
+  }
+
+  async acceptOrganizationInvite(acceptInviteDto: AcceptInviteDto) {
+    const { password, token } = acceptInviteDto;
+
+    if (this.configService.get<string>('DISABLE_MULTI_WORKSPACE') === 'true' && !password) {
+      throw new BadRequestException('Please enter password');
+    }
+    const organizationUser = await this.organizationUsersRepository.findOne({
+      where: { invitationToken: token },
+      relations: ['user'],
+    });
+
+    if (!organizationUser?.user) {
+      throw new BadRequestException('Invalid invitation link');
+    }
+    const user: User = organizationUser.user;
+
+    if (this.configService.get<string>('DISABLE_MULTI_WORKSPACE') !== 'true' && user.invitationToken) {
+      // User sign up link send - not activated account
+      this.emailService
+        .sendWelcomeEmail(
+          user.email,
+          `${user.firstName} ${user.lastName}`,
+          user.invitationToken,
+          organizationUser.invitationToken
+        )
+        .catch((err) => console.error('Error while sending welcome mail', err));
+      throw new UnauthorizedException(
+        'User not exist in the workspace, Please setup your account using link shared via email'
+      );
+    }
+
+    if (this.configService.get<string>('DISABLE_MULTI_WORKSPACE') === 'true') {
+      // set new password
+      await this.usersRepository.save(
+        Object.assign(user, {
+          ...(password ? { password } : {}),
+          invitationToken: null,
+        })
+      );
+    }
+
+    await this.organizationUsersRepository.save(
+      Object.assign(organizationUser, {
+        invitationToken: null,
+        status: 'active',
+      })
+    );
+  }
 }

programming_language: TypeScript
filename: email.service.ts
change_type: ModificationType.MODIFY
diff: @@ -53,9 +53,18 @@ export class EmailService {
     return hostname?.endsWith('/') ? hostname.slice(0, -1) : hostname;
   }
 
-  async sendWelcomeEmail(to: string, name: string, invitationtoken: string) {
+  async sendWelcomeEmail(
+    to: string,
+    name: string,
+    invitationtoken: string,
+    organizationInvitationToken?: string,
+    organizationName?: string,
+    sender?: string
+  ) {
     const subject = 'Welcome to ToolJet';
-    const inviteUrl = `${this.TOOLJET_HOST}/invitations/${invitationtoken}`;
+    const inviteUrl = `${this.TOOLJET_HOST}/invitations/${invitationtoken}${
+      organizationInvitationToken ? `/workspaces/${organizationInvitationToken}` : ''
+    }`;
     const html = `
       <!DOCTYPE html>
       <html>
@@ -64,6 +73,13 @@ export class EmailService {
         </head>
         <body>
           <p>Hi ${name || ''},</p>
+          ${
+            organizationInvitationToken && sender && organizationName
+              ? `<span>
+              ${sender} has invited you to use ToolJet workspace: ${organizationName}.
+            </span>`
+              : ''
+          }
           <span>
             Please use the link below to set up your account and get started.
           </span>
@@ -86,7 +102,7 @@ export class EmailService {
     name: string,
     sender: string,
     invitationtoken: string,
-    organisationName: string
+    organizationName: string
   ) {
     const subject = 'Welcome to ToolJet';
     const inviteUrl = `${this.TOOLJET_HOST}/organization-invitations/${invitationtoken}`;
@@ -100,7 +116,7 @@ export class EmailService {
           <p>Hi ${name || ''},</p>
           <br>
           <span>
-          ${sender} has invited you to use ToolJet workspace ${organisationName}. Use the link below to set up your account and get started.
+          ${sender} has invited you to use ToolJet workspace: ${organizationName}. Use the link below to set up your account and get started.
           </span>
           <br>
           <a href="${inviteUrl}">${inviteUrl}</a>

programming_language: TypeScript
filename: organization_users.service.ts
change_type: ModificationType.MODIFY
diff: @@ -8,7 +8,6 @@ import { BadRequestException } from '@nestjs/common';
 import { EmailService } from './email.service';
 import { Organization } from 'src/entities/organization.entity';
 import { GroupPermission } from 'src/entities/group_permission.entity';
-import { InviteNewUserDto } from '@dto/invite-new-user.dto';
 const uuid = require('uuid');
 
 @Injectable()
@@ -24,46 +23,6 @@ export class OrganizationUsersService {
     return await this.organizationUsersRepository.findOne({ where: { id } });
   }
 
-  async inviteNewUser(currentUser: User, inviteNewUserDto: InviteNewUserDto): Promise<OrganizationUser> {
-    const userParams = <User>{
-      firstName: inviteNewUserDto.first_name,
-      lastName: inviteNewUserDto.last_name,
-      email: inviteNewUserDto.email,
-    };
-
-    let user = await this.usersService.findByEmail(userParams.email);
-
-    if (user?.organizationUsers?.some((ou) => ou.organizationId === currentUser.organizationId)) {
-      throw new BadRequestException('User with such email already exists.');
-    }
-
-    if (user?.invitationToken) {
-      // user sign up not completed, name will be empty - updating name
-      await this.usersService.update(user.id, { firstName: userParams.firstName, lastName: userParams.lastName });
-    }
-
-    user = await this.usersService.create(userParams, currentUser.organizationId, ['all_users'], user);
-
-    const currentOrganization: Organization = (
-      await this.organizationUsersRepository.findOne({
-        where: { userId: currentUser.id, organizationId: currentUser.organizationId },
-        relations: ['organization'],
-      })
-    )?.organization;
-
-    const organizationUser: OrganizationUser = await this.create(user, currentOrganization, true);
-
-    await this.emailService.sendOrganizationUserWelcomeEmail(
-      user.email,
-      user.firstName,
-      currentUser.firstName,
-      organizationUser.invitationToken,
-      currentOrganization.name
-    );
-
-    return organizationUser;
-  }
-
   async create(user: User, organization: Organization, isInvite?: boolean): Promise<OrganizationUser> {
     return await this.organizationUsersRepository.save(
       this.organizationUsersRepository.create({

programming_language: TypeScript
filename: organizations.service.ts
change_type: ModificationType.MODIFY
diff: @@ -7,10 +7,13 @@ import { User } from 'src/entities/user.entity';
 import { cleanObject } from 'src/helpers/utils.helper';
 import { createQueryBuilder, Repository } from 'typeorm';
 import { OrganizationUser } from '../entities/organization_user.entity';
+import { EmailService } from './email.service';
 import { EncryptionService } from './encryption.service';
 import { GroupPermissionsService } from './group_permissions.service';
 import { OrganizationUsersService } from './organization_users.service';
 import { UsersService } from './users.service';
+import { InviteNewUserDto } from '@dto/invite-new-user.dto';
+import { ConfigService } from '@nestjs/config';
 
 @Injectable()
 export class OrganizationsService {
@@ -26,7 +29,9 @@ export class OrganizationsService {
     private usersService: UsersService,
     private organizationUserService: OrganizationUsersService,
     private groupPermissionService: GroupPermissionsService,
-    private encryptionService: EncryptionService
+    private encryptionService: EncryptionService,
+    private emailService: EmailService,
+    private configService: ConfigService
   ) {}
 
   async create(name: string, user?: User): Promise<Organization> {
@@ -308,4 +313,83 @@ export class OrganizationsService {
     await this.decryptSecret(result?.configs);
     return result;
   }
+
+  async inviteNewUser(currentUser: User, inviteNewUserDto: InviteNewUserDto): Promise<OrganizationUser> {
+    const userParams = <User>{
+      firstName: inviteNewUserDto.first_name,
+      lastName: inviteNewUserDto.last_name,
+      email: inviteNewUserDto.email,
+    };
+
+    let user = await this.usersService.findByEmail(userParams.email);
+    let defaultOrganisation: Organization,
+      shouldSendWelcomeMail = false;
+
+    if (user?.organizationUsers?.some((ou) => ou.organizationId === currentUser.organizationId)) {
+      throw new BadRequestException('User with such email already exists.');
+    }
+
+    if (user?.invitationToken) {
+      // user sign up not completed, name will be empty - updating name
+      await this.usersService.update(user.id, { firstName: userParams.firstName, lastName: userParams.lastName });
+    }
+
+    if (!user && this.configService.get<string>('DISABLE_MULTI_WORKSPACE') !== 'true') {
+      // User not exist
+      shouldSendWelcomeMail = true;
+      // Create default organization
+      defaultOrganisation = await this.create('Untitled workspace');
+    }
+    user = await this.usersService.create(
+      userParams,
+      currentUser.organizationId,
+      ['all_users'],
+      user,
+      true,
+      defaultOrganisation?.id
+    );
+
+    if (defaultOrganisation) {
+      // Setting up default organization
+      await this.organizationUserService.create(user, defaultOrganisation, true);
+      await this.usersService.attachUserGroup(['all_users', 'admin'], defaultOrganisation.id, user.id);
+    }
+
+    const currentOrganization: Organization = (
+      await this.organizationUsersRepository.findOne({
+        where: { userId: currentUser.id, organizationId: currentUser.organizationId },
+        relations: ['organization'],
+      })
+    )?.organization;
+
+    const organizationUser: OrganizationUser = await this.organizationUserService.create(
+      user,
+      currentOrganization,
+      true
+    );
+
+    if (shouldSendWelcomeMail) {
+      this.emailService
+        .sendWelcomeEmail(
+          user.email,
+          user.firstName,
+          user.invitationToken,
+          organizationUser.invitationToken,
+          currentOrganization.name,
+          `${currentUser.firstName} ${currentUser.lastName}`
+        )
+        .catch((err) => console.error('Error while sending welcome mail', err));
+    } else {
+      this.emailService
+        .sendOrganizationUserWelcomeEmail(
+          user.email,
+          user.firstName,
+          `${currentUser.firstName} ${currentUser.lastName}`,
+          organizationUser.invitationToken,
+          currentOrganization.name
+        )
+        .catch((err) => console.error('Error while sending welcome mail', err));
+    }
+    return organizationUser;
+  }
 }

programming_language: TypeScript
filename: users.service.ts
change_type: ModificationType.MODIFY
diff: @@ -2,7 +2,6 @@ import { Injectable, InternalServerErrorException } from '@nestjs/common';
 import { InjectRepository } from '@nestjs/typeorm';
 import { User } from '../entities/user.entity';
 import { FilesService } from '../services/files.service';
-import { Organization } from 'src/entities/organization.entity';
 import { App } from 'src/entities/app.entity';
 import { Connection, createQueryBuilder, EntityManager, getManager, getRepository, In, Repository } from 'typeorm';
 import { OrganizationUser } from '../entities/organization_user.entity';
@@ -11,7 +10,6 @@ import { UserGroupPermission } from 'src/entities/user_group_permission.entity';
 import { GroupPermission } from 'src/entities/group_permission.entity';
 import { BadRequestException } from '@nestjs/common';
 import { cleanObject } from 'src/helpers/utils.helper';
-import { CreateUserDto } from '@dto/user.dto';
 import { CreateFileDto } from '@dto/create-file.dto';
 const uuid = require('uuid');
 const bcrypt = require('bcrypt');
@@ -25,8 +23,6 @@ export class UsersService {
     private usersRepository: Repository<User>,
     @InjectRepository(OrganizationUser)
     private organizationUsersRepository: Repository<OrganizationUser>,
-    @InjectRepository(Organization)
-    private organizationsRepository: Repository<Organization>,
     @InjectRepository(App)
     private appsRepository: Repository<App>
   ) {}
@@ -65,7 +61,8 @@ export class UsersService {
     organizationId: string,
     groups?: string[],
     existingUser?: User,
-    isInvite?: boolean
+    isInvite?: boolean,
+    defaultOrganizationId?: string
   ): Promise<User> {
     const password = uuid.v4();
 
@@ -80,24 +77,23 @@ export class UsersService {
           lastName,
           password,
           invitationToken: isInvite ? uuid.v4() : null,
-          defaultOrganizationId: organizationId,
+          defaultOrganizationId: defaultOrganizationId || organizationId,
           createdAt: new Date(),
           updatedAt: new Date(),
         });
         await manager.save(user);
       } else {
-        if (isInvite) {
-          // user already invited to an organization, but not active - user tries to sign up
-          await manager.save(
-            Object.assign(existingUser, {
-              invitationToken: uuid.v4(),
-              defaultOrganizationId: organizationId,
-            })
-          );
-        }
         user = existingUser;
       }
+    });
+
+    await this.attachUserGroup(groups, organizationId, user.id);
+
+    return user;
+  }
 
+  async attachUserGroup(groups, organizationId, userId) {
+    await getManager().transaction(async (manager) => {
       for (const group of groups) {
         const orgGroupPermission = await manager.findOne(GroupPermission, {
           where: {
@@ -109,7 +105,7 @@ export class UsersService {
         if (orgGroupPermission) {
           const userGroupPermission = manager.create(UserGroupPermission, {
             groupPermissionId: orgGroupPermission.id,
-            userId: user.id,
+            userId: userId,
           });
           await manager.save(userGroupPermission);
         } else {
@@ -117,8 +113,6 @@ export class UsersService {
         }
       }
     });
-
-    return user;
   }
 
   async status(user: User) {
@@ -144,95 +138,6 @@ export class UsersService {
     return { user, newUserCreated };
   }
 
-  async setupAccountFromInvitationToken(userCreateDto: CreateUserDto) {
-    const { organization, password, token, role, first_name: firstName, last_name: lastName } = userCreateDto;
-
-    if (!token) {
-      throw new BadRequestException('Invalid token');
-    }
-
-    const user: User = await this.usersRepository.findOne({ where: { invitationToken: token } });
-
-    if (!user?.organizationUsers) {
-      throw new BadRequestException('Invalid invitation link');
-    }
-    const organizationUser: OrganizationUser = user.organizationUsers.find(
-      (ou) => ou.organizationId === user.defaultOrganizationId
-    );
-
-    if (!organizationUser) {
-      throw new BadRequestException('Invalid invitation link');
-    }
-
-    await this.usersRepository.save(
-      Object.assign(user, {
-        firstName,
-        lastName,
-        password,
-        role,
-        invitationToken: null,
-      })
-    );
-
-    await this.organizationUsersRepository.save(
-      Object.assign(organizationUser, {
-        invitationToken: null,
-        status: 'active',
-      })
-    );
-
-    if (organization) {
-      await this.organizationsRepository.update(user.defaultOrganizationId, {
-        name: organization,
-      });
-    }
-  }
-
-  async acceptOrganizationInvite(params: any) {
-    const { password, token } = params;
-
-    const organizationUser = await this.organizationUsersRepository.findOne({
-      where: { invitationToken: token },
-      relations: ['user'],
-    });
-
-    if (!organizationUser?.user) {
-      throw new BadRequestException('Invalid invitation link');
-    }
-    const user: User = organizationUser.user;
-
-    if (user.invitationToken) {
-      // User sign up link send - not activated account
-      const defaultOrganizationUser = await this.organizationUsersRepository.findOne({
-        where: { organizationId: user.defaultOrganizationId, status: 'invited' },
-      });
-
-      if (defaultOrganizationUser) {
-        await this.organizationUsersRepository.save(
-          Object.assign(defaultOrganizationUser, {
-            invitationToken: null,
-            status: 'active',
-          })
-        );
-      }
-    }
-
-    // set new password if entered
-    await this.usersRepository.save(
-      Object.assign(user, {
-        ...(password ? { password } : {}),
-        invitationToken: null,
-      })
-    );
-
-    await this.organizationUsersRepository.save(
-      Object.assign(organizationUser, {
-        invitationToken: null,
-        status: 'active',
-      })
-    );
-  }
-
   async updateDefaultOrganization(user: User, organizationId: string) {
     await this.usersRepository.update(user.id, { defaultOrganizationId: organizationId });
   }

programming_language: TypeScript
filename: app.e2e-spec.ts
change_type: ModificationType.MODIFY
diff: @@ -1,13 +1,14 @@
 /* eslint-disable @typescript-eslint/no-unused-vars */
 import * as request from 'supertest';
 import { INestApplication } from '@nestjs/common';
-import { getManager, Repository } from 'typeorm';
+import { getManager, Repository, Not } from 'typeorm';
 import { User } from 'src/entities/user.entity';
 import { clearDB, createUser, authHeaderForUser, createNestAppInstanceWithEnvMock } from '../test.helper';
 import { OrganizationUser } from 'src/entities/organization_user.entity';
 import { Organization } from 'src/entities/organization.entity';
 import { SSOConfigs } from 'src/entities/sso_config.entity';
 import { EmailService } from '@services/email.service';
+import { v4 as uuidv4 } from 'uuid';
 
 describe('Authentication', () => {
   let app: INestApplication;
@@ -172,7 +173,7 @@ describe('Authentication', () => {
       });
       it('should not create new users', async () => {
         const response = await request(app.getHttpServer()).post('/api/signup').send({ email: 'test@tooljet.io' });
-        expect(response.statusCode).toBe(406);
+        expect(response.statusCode).toBe(403);
       });
     });
     describe('sign up enabled and authorization', () => {
@@ -466,6 +467,393 @@ describe('Authentication', () => {
     });
   });
 
+  describe('POST /api/set-password-from-token', () => {
+    beforeEach(() => {
+      jest.spyOn(mockConfig, 'get').mockImplementation((key: string) => {
+        switch (key) {
+          case 'DISABLE_MULTI_WORKSPACE':
+            return 'false';
+          default:
+            return process.env[key];
+        }
+      });
+    });
+    it('should allow users to setup account after sign up using  Multi-Workspace', async () => {
+      const invitationToken = uuidv4();
+      const userData = await createUser(app, {
+        email: 'signup@tooljet.io',
+        invitationToken,
+        status: 'invited',
+      });
+      const { user, organization } = userData;
+
+      const response = await request(app.getHttpServer()).post('/api/set-password-from-token').send({
+        first_name: 'signupuser',
+        last_name: 'user',
+        organization: 'org1',
+        password: uuidv4(),
+        token: invitationToken,
+        role: 'developer',
+      });
+
+      expect(response.statusCode).toBe(201);
+
+      const updatedUser = await getManager().findOneOrFail(User, { where: { email: user.email } });
+      expect(updatedUser.firstName).toEqual('signupuser');
+      expect(updatedUser.lastName).toEqual('user');
+      expect(updatedUser.defaultOrganizationId).toEqual(organization.id);
+      const organizationUser = await getManager().findOneOrFail(OrganizationUser, { where: { userId: user.id } });
+      expect(organizationUser.status).toEqual('active');
+    });
+
+    it('should return error if required params are not present - Multi-Workspace', async () => {
+      const invitationToken = uuidv4();
+      await createUser(app, {
+        email: 'signup@tooljet.io',
+        invitationToken,
+        status: 'invited',
+      });
+
+      const response = await request(app.getHttpServer()).post('/api/set-password-from-token');
+
+      expect(response.statusCode).toBe(400);
+      expect(response.body.message).toStrictEqual([
+        'password should not be empty',
+        'password must be a string',
+        'token should not be empty',
+        'token must be a string',
+      ]);
+    });
+
+    it('should allow users to setup account for single organization only once', async () => {
+      jest.spyOn(mockConfig, 'get').mockImplementation((key: string) => {
+        switch (key) {
+          case 'DISABLE_MULTI_WORKSPACE':
+            return 'true';
+          default:
+            return process.env[key];
+        }
+      });
+      const invitationToken = uuidv4();
+      await createUser(app, {
+        email: 'signup@tooljet.io',
+        invitationToken,
+        status: 'invited',
+      });
+
+      let response = await request(app.getHttpServer()).post('/api/set-password-from-token').send({
+        first_name: 'signupuser',
+        last_name: 'user',
+        organization: 'org1',
+        password: uuidv4(),
+        token: invitationToken,
+        role: 'developer',
+      });
+
+      expect(response.statusCode).toBe(201);
+
+      await createUser(app, {
+        email: 'signup2@tooljet.io',
+        invitationToken,
+        status: 'invited',
+      });
+
+      response = await request(app.getHttpServer()).post('/api/set-password-from-token').send({
+        first_name: 'signupuser2',
+        last_name: 'user2',
+        organization: 'org1',
+        password: uuidv4(),
+        token: invitationToken,
+        role: 'developer',
+      });
+
+      expect(response.statusCode).toBe(403);
+    });
+
+    it('should not allow users to setup account for Multi-Workspace and sign up disabled', async () => {
+      jest.spyOn(mockConfig, 'get').mockImplementation((key: string) => {
+        switch (key) {
+          case 'DISABLE_SIGNUPS':
+            return 'true';
+          default:
+            return process.env[key];
+        }
+      });
+      const invitationToken = uuidv4();
+      await createUser(app, {
+        email: 'signup@tooljet.io',
+        invitationToken,
+        status: 'invited',
+      });
+
+      const response = await request(app.getHttpServer()).post('/api/set-password-from-token').send({
+        first_name: 'signupuser',
+        last_name: 'user',
+        organization: 'org1',
+        password: uuidv4(),
+        token: invitationToken,
+        role: 'developer',
+      });
+
+      expect(response.statusCode).toBe(403);
+    });
+
+    it('should allow users to sign up and setup account if already invited to an organization but not setup the account', async () => {
+      const { organization: org, user: adminUser } = await createUser(app, {
+        email: 'admin@tooljet.io',
+      });
+
+      await request(app.getHttpServer())
+        .post(`/api/organization_users/`)
+        .set('Authorization', authHeaderForUser(adminUser))
+        .send({ email: 'invited@tooljet.io' })
+        .expect(201);
+
+      const signUpResponse = await request(app.getHttpServer())
+        .post('/api/signup')
+        .send({ email: 'invited@tooljet.io' });
+
+      expect(signUpResponse.statusCode).toBe(201);
+
+      const invitedUserDetails = await getManager().findOneOrFail(User, { where: { email: 'invited@tooljet.io' } });
+
+      expect(invitedUserDetails.defaultOrganizationId).not.toBe(org.id);
+
+      const response = await request(app.getHttpServer()).post('/api/set-password-from-token').send({
+        first_name: 'signupuser',
+        last_name: 'user',
+        organization: 'org1',
+        password: uuidv4(),
+        token: invitedUserDetails.invitationToken,
+        role: 'developer',
+      });
+
+      expect(response.statusCode).toBe(201);
+      const updatedUser = await getManager().findOneOrFail(User, { where: { email: 'invited@tooljet.io' } });
+      expect(updatedUser.firstName).toEqual('signupuser');
+      expect(updatedUser.lastName).toEqual('user');
+      expect(updatedUser.defaultOrganizationId).not.toBe(org.id);
+      const organizationUser = await getManager().findOneOrFail(OrganizationUser, {
+        where: { userId: Not(adminUser.id), organizationId: org.id },
+      });
+      const defaultOrganizationUser = await getManager().findOneOrFail(OrganizationUser, {
+        where: { userId: Not(adminUser.id), organizationId: invitedUserDetails.defaultOrganizationId },
+      });
+      expect(organizationUser.status).toEqual('invited');
+      expect(defaultOrganizationUser.status).toEqual('active');
+
+      const acceptInviteResponse = await request(app.getHttpServer()).post('/api/accept-invite').send({
+        token: organizationUser.invitationToken,
+      });
+
+      expect(acceptInviteResponse.statusCode).toBe(201);
+
+      const organizationUserUpdated = await getManager().findOneOrFail(OrganizationUser, {
+        where: { userId: Not(adminUser.id), organizationId: org.id },
+      });
+      expect(organizationUserUpdated.status).toEqual('active');
+    });
+
+    it('should allow users setup account and accept invite', async () => {
+      const { organization: org, user: adminUser } = await createUser(app, {
+        email: 'admin@tooljet.io',
+      });
+
+      await request(app.getHttpServer())
+        .post(`/api/organization_users/`)
+        .set('Authorization', authHeaderForUser(adminUser))
+        .send({ email: 'invited@tooljet.io' })
+        .expect(201);
+
+      const invitedUserDetails = await getManager().findOneOrFail(User, { where: { email: 'invited@tooljet.io' } });
+
+      expect(invitedUserDetails.defaultOrganizationId).not.toBe(org.id);
+
+      const organizationUserBeforeUpdate = await getManager().findOneOrFail(OrganizationUser, {
+        where: { userId: Not(adminUser.id), organizationId: org.id },
+      });
+
+      const response = await request(app.getHttpServer()).post('/api/set-password-from-token').send({
+        first_name: 'signupuser',
+        last_name: 'user',
+        organization: 'org1',
+        password: uuidv4(),
+        token: invitedUserDetails.invitationToken,
+        organizationToken: organizationUserBeforeUpdate.invitationToken,
+        role: 'developer',
+      });
+
+      expect(response.statusCode).toBe(201);
+      const updatedUser = await getManager().findOneOrFail(User, { where: { email: 'invited@tooljet.io' } });
+      expect(updatedUser.firstName).toEqual('signupuser');
+      expect(updatedUser.lastName).toEqual('user');
+      expect(updatedUser.defaultOrganizationId).not.toBe(org.id);
+      const organizationUser = await getManager().findOneOrFail(OrganizationUser, {
+        where: { userId: Not(adminUser.id), organizationId: org.id },
+      });
+      const defaultOrganizationUser = await getManager().findOneOrFail(OrganizationUser, {
+        where: { userId: Not(adminUser.id), organizationId: invitedUserDetails.defaultOrganizationId },
+      });
+      expect(organizationUser.status).toEqual('active');
+      expect(defaultOrganizationUser.status).toEqual('active');
+
+      const acceptInviteResponse = await request(app.getHttpServer()).post('/api/accept-invite').send({
+        token: organizationUser.invitationToken,
+      });
+
+      expect(acceptInviteResponse.statusCode).toBe(400);
+    });
+
+    it('should not allow users to setup account if already invited to an organization and trying to accept invite before setting up account', async () => {
+      const { organization: org, user: adminUser } = await createUser(app, {
+        email: 'admin@tooljet.io',
+      });
+
+      await request(app.getHttpServer())
+        .post(`/api/organization_users/`)
+        .set('Authorization', authHeaderForUser(adminUser))
+        .send({ email: 'invited@tooljet.io' })
+        .expect(201);
+
+      const signUpResponse = await request(app.getHttpServer())
+        .post('/api/signup')
+        .send({ email: 'invited@tooljet.io' });
+
+      expect(signUpResponse.statusCode).toBe(201);
+
+      const invitedUserDetails = await getManager().findOneOrFail(User, { where: { email: 'invited@tooljet.io' } });
+      const orgUser = await getManager().findOneOrFail(OrganizationUser, {
+        where: { userId: invitedUserDetails.id, organizationId: org.id },
+      });
+
+      expect(invitedUserDetails.defaultOrganizationId).not.toBe(org.id);
+
+      const acceptInviteResponse = await request(app.getHttpServer()).post('/api/accept-invite').send({
+        token: orgUser.invitationToken,
+      });
+
+      expect(acceptInviteResponse.statusCode).toBe(401);
+
+      const organizationUser = await getManager().findOneOrFail(OrganizationUser, {
+        where: { userId: invitedUserDetails.id, organizationId: org.id },
+      });
+      const defaultOrganizationUser = await getManager().findOneOrFail(OrganizationUser, {
+        where: { userId: invitedUserDetails.id, organizationId: invitedUserDetails.defaultOrganizationId },
+      });
+      expect(organizationUser.status).toEqual('invited');
+      expect(defaultOrganizationUser.status).toEqual('invited');
+
+      const response = await request(app.getHttpServer()).post('/api/set-password-from-token').send({
+        first_name: 'signupuser',
+        last_name: 'user',
+        organization: 'org1',
+        password: uuidv4(),
+        token: invitedUserDetails.invitationToken,
+        role: 'developer',
+      });
+
+      expect(response.statusCode).toBe(201);
+    });
+  });
+
+  describe('POST /api/accept-invite', () => {
+    describe('Multi-Worlspace Enabled', () => {
+      beforeEach(() => {
+        jest.spyOn(mockConfig, 'get').mockImplementation((key: string) => {
+          switch (key) {
+            case 'DISABLE_MULTI_WORKSPACE':
+              return 'false';
+            default:
+              return process.env[key];
+          }
+        });
+      });
+
+      it('should allow users to accept invitation when Multi-Workspace is enabled', async () => {
+        const userData = await createUser(app, {
+          email: 'organizationUser@tooljet.io',
+          status: 'invited',
+        });
+
+        const { user, orgUser } = userData;
+
+        const response = await request(app.getHttpServer()).post('/api/accept-invite').send({
+          token: orgUser.invitationToken,
+        });
+
+        expect(response.statusCode).toBe(201);
+
+        const organizationUser = await getManager().findOneOrFail(OrganizationUser, { where: { userId: user.id } });
+        expect(organizationUser.status).toEqual('active');
+      });
+
+      it('should not allow users to accept invitation when user sign up is not completed', async () => {
+        const userData = await createUser(app, {
+          email: 'organizationUser@tooljet.io',
+          invitationToken: uuidv4(),
+          status: 'invited',
+        });
+        const { user, orgUser } = userData;
+
+        const response = await request(app.getHttpServer()).post('/api/accept-invite').send({
+          token: orgUser.invitationToken,
+        });
+
+        expect(response.statusCode).toBe(401);
+        expect(response.body.message).toBe(
+          'User not exist in the workspace, Please setup your account using link shared via email'
+        );
+      });
+    });
+
+    describe('Multi-Worlspace Disabled', () => {
+      beforeEach(() => {
+        jest.spyOn(mockConfig, 'get').mockImplementation((key: string) => {
+          switch (key) {
+            case 'DISABLE_MULTI_WORKSPACE':
+              return 'true';
+            default:
+              return process.env[key];
+          }
+        });
+      });
+
+      it('should allow users to accept invitation when Multi-Workspace is disabled', async () => {
+        const userData = await createUser(app, {
+          email: 'organizationUser@tooljet.io',
+          status: 'invited',
+        });
+        const { user, orgUser } = userData;
+
+        const response = await request(app.getHttpServer()).post('/api/accept-invite').send({
+          token: orgUser.invitationToken,
+          password: uuidv4(),
+        });
+
+        expect(response.statusCode).toBe(201);
+
+        const organizationUser = await getManager().findOneOrFail(OrganizationUser, { where: { userId: user.id } });
+        expect(organizationUser.status).toEqual('active');
+      });
+
+      it('should not allow users to accept invitation when user not entered password for single workspace', async () => {
+        const userData = await createUser(app, {
+          email: 'organizationUser@tooljet.io',
+          invitationToken: uuidv4(),
+          status: 'invited',
+        });
+        const { orgUser } = userData;
+
+        const response = await request(app.getHttpServer()).post('/api/accept-invite').send({
+          token: orgUser.invitationToken,
+        });
+
+        expect(response.statusCode).toBe(400);
+        expect(response.body.message).toBe('Please enter password');
+      });
+    });
+  });
+
   afterAll(async () => {
     await app.close();
   });

programming_language: TypeScript
filename: users.e2e-spec.ts
change_type: ModificationType.MODIFY
diff: @@ -1,22 +1,19 @@
 import * as request from 'supertest';
 import { INestApplication } from '@nestjs/common';
-import { authHeaderForUser, clearDB, createUser, createNestAppInstanceWithEnvMock } from '../test.helper';
+import { authHeaderForUser, clearDB, createUser, createNestAppInstance } from '../test.helper';
 import { getManager } from 'typeorm';
 import { User } from 'src/entities/user.entity';
-import { v4 as uuidv4 } from 'uuid';
-import { OrganizationUser } from 'src/entities/organization_user.entity';
 const path = require('path');
 
 describe('users controller', () => {
   let app: INestApplication;
-  let mockConfig;
 
   beforeEach(async () => {
     await clearDB();
   });
 
   beforeAll(async () => {
-    ({ app, mockConfig } = await createNestAppInstanceWithEnvMock());
+    app = await createNestAppInstance();
   });
 
   afterEach(() => {
@@ -82,271 +79,6 @@ describe('users controller', () => {
     });
   });
 
-  describe('POST /api/users/set_password_from_token', () => {
-    it('should allow users to setup account after sign up using  Multi-Workspace', async () => {
-      const invitationToken = uuidv4();
-      const userData = await createUser(app, {
-        email: 'signup@tooljet.io',
-        invitationToken,
-        status: 'invited',
-      });
-      const { user, organization } = userData;
-
-      const response = await request(app.getHttpServer()).post('/api/users/set_password_from_token').send({
-        first_name: 'signupuser',
-        last_name: 'user',
-        organization: 'org1',
-        password: uuidv4(),
-        token: invitationToken,
-        role: 'developer',
-      });
-
-      expect(response.statusCode).toBe(201);
-
-      const updatedUser = await getManager().findOneOrFail(User, { where: { email: user.email } });
-      expect(updatedUser.firstName).toEqual('signupuser');
-      expect(updatedUser.lastName).toEqual('user');
-      expect(updatedUser.defaultOrganizationId).toEqual(organization.id);
-      const organizationUser = await getManager().findOneOrFail(OrganizationUser, { where: { userId: user.id } });
-      expect(organizationUser.status).toEqual('active');
-    });
-
-    it('should return error if required params are not present - Multi-Workspace', async () => {
-      const invitationToken = uuidv4();
-      await createUser(app, {
-        email: 'signup@tooljet.io',
-        invitationToken,
-        status: 'invited',
-      });
-
-      const response = await request(app.getHttpServer()).post('/api/users/set_password_from_token');
-
-      expect(response.statusCode).toBe(400);
-      expect(response.body.message).toStrictEqual([
-        'password should not be empty',
-        'password must be a string',
-        'token should not be empty',
-        'token must be a string',
-      ]);
-    });
-
-    it('should allow users to setup account for single organization only once', async () => {
-      jest.spyOn(mockConfig, 'get').mockImplementation((key: string) => {
-        switch (key) {
-          case 'DISABLE_MULTI_WORKSPACE':
-            return 'true';
-          default:
-            return process.env[key];
-        }
-      });
-      const invitationToken = uuidv4();
-      await createUser(app, {
-        email: 'signup@tooljet.io',
-        invitationToken,
-        status: 'invited',
-      });
-
-      let response = await request(app.getHttpServer()).post('/api/users/set_password_from_token').send({
-        first_name: 'signupuser',
-        last_name: 'user',
-        organization: 'org1',
-        password: uuidv4(),
-        token: invitationToken,
-        role: 'developer',
-      });
-
-      expect(response.statusCode).toBe(201);
-
-      await createUser(app, {
-        email: 'signup2@tooljet.io',
-        invitationToken,
-        status: 'invited',
-      });
-
-      response = await request(app.getHttpServer()).post('/api/users/set_password_from_token').send({
-        first_name: 'signupuser2',
-        last_name: 'user2',
-        organization: 'org1',
-        password: uuidv4(),
-        token: invitationToken,
-        role: 'developer',
-      });
-
-      expect(response.statusCode).toBe(403);
-    });
-
-    it('should not allow users to setup account for Multi-Workspace and sign up disabled', async () => {
-      jest.spyOn(mockConfig, 'get').mockImplementation((key: string) => {
-        switch (key) {
-          case 'DISABLE_SIGNUPS':
-            return 'true';
-          default:
-            return process.env[key];
-        }
-      });
-      const invitationToken = uuidv4();
-      await createUser(app, {
-        email: 'signup@tooljet.io',
-        invitationToken,
-        status: 'invited',
-      });
-
-      const response = await request(app.getHttpServer()).post('/api/users/set_password_from_token').send({
-        first_name: 'signupuser',
-        last_name: 'user',
-        organization: 'org1',
-        password: uuidv4(),
-        token: invitationToken,
-        role: 'developer',
-      });
-
-      expect(response.statusCode).toBe(403);
-    });
-
-    it('should allow users to setup account if already invited to an organization but not activated', async () => {
-      const org = (
-        await createUser(app, {
-          email: 'admin@tooljet.io',
-        })
-      ).organization;
-      const invitedUser = await createUser(app, {
-        email: 'invited@tooljet.io',
-        status: 'invited',
-        organization: org,
-      });
-
-      const signUpResponse = await request(app.getHttpServer())
-        .post('/api/signup')
-        .send({ email: 'invited@tooljet.io' });
-
-      expect(signUpResponse.statusCode).toBe(201);
-
-      const invitedUserDetails = await getManager().findOneOrFail(User, { where: { email: invitedUser.user.email } });
-
-      expect(invitedUserDetails.defaultOrganizationId).not.toBe(org.id);
-
-      const response = await request(app.getHttpServer()).post('/api/users/set_password_from_token').send({
-        first_name: 'signupuser',
-        last_name: 'user',
-        organization: 'org1',
-        password: uuidv4(),
-        token: invitedUserDetails.invitationToken,
-        role: 'developer',
-      });
-
-      expect(response.statusCode).toBe(201);
-      const updatedUser = await getManager().findOneOrFail(User, { where: { email: invitedUser.user.email } });
-      expect(updatedUser.firstName).toEqual('signupuser');
-      expect(updatedUser.lastName).toEqual('user');
-      expect(updatedUser.defaultOrganizationId).not.toBe(org.id);
-      const organizationUser = await getManager().findOneOrFail(OrganizationUser, {
-        where: { userId: invitedUser.user.id, organizationId: org.id },
-      });
-      const defaultOrganizationUser = await getManager().findOneOrFail(OrganizationUser, {
-        where: { userId: invitedUser.user.id, organizationId: invitedUserDetails.defaultOrganizationId },
-      });
-      expect(organizationUser.status).toEqual('invited');
-      expect(defaultOrganizationUser.status).toEqual('active');
-    });
-
-    it('should not allow users to setup account if already invited to an organization and activated account through invite link after sign up', async () => {
-      const { organization: org } = await createUser(app, {
-        email: 'admin@tooljet.io',
-      });
-      const invitedUser = await createUser(app, {
-        email: 'invited@tooljet.io',
-        status: 'invited',
-        organization: org,
-      });
-
-      const signUpResponse = await request(app.getHttpServer())
-        .post('/api/signup')
-        .send({ email: 'invited@tooljet.io' });
-
-      expect(signUpResponse.statusCode).toBe(201);
-
-      const invitedUserDetails = await getManager().findOneOrFail(User, { where: { email: invitedUser.user.email } });
-
-      expect(invitedUserDetails.defaultOrganizationId).not.toBe(org.id);
-
-      const acceptInviteResponse = await request(app.getHttpServer()).post('/api/users/accept-invite').send({
-        token: invitedUser.orgUser.invitationToken,
-        password: 'new-password',
-      });
-
-      expect(acceptInviteResponse.statusCode).toBe(201);
-
-      const organizationUser = await getManager().findOneOrFail(OrganizationUser, {
-        where: { userId: invitedUser.user.id, organizationId: org.id },
-      });
-      const defaultOrganizationUser = await getManager().findOneOrFail(OrganizationUser, {
-        where: { userId: invitedUser.user.id, organizationId: invitedUserDetails.defaultOrganizationId },
-      });
-      expect(organizationUser.status).toEqual('active');
-      expect(defaultOrganizationUser.status).toEqual('active');
-
-      const updatedUser = await getManager().findOneOrFail(User, { where: { email: invitedUser.user.email } });
-      expect(updatedUser.defaultOrganizationId).toBe(defaultOrganizationUser.organizationId);
-
-      const response = await request(app.getHttpServer()).post('/api/users/set_password_from_token').send({
-        first_name: 'signupuser',
-        last_name: 'user',
-        organization: 'org1',
-        password: uuidv4(),
-        token: invitedUserDetails.invitationToken,
-        role: 'developer',
-      });
-
-      expect(response.statusCode).toBe(400);
-    });
-  });
-
-  describe('POST /api/users/accept-invite', () => {
-    it('should allow users to accept invitation when Multi-Workspace is enabled', async () => {
-      const userData = await createUser(app, {
-        email: 'organizationUser@tooljet.io',
-        status: 'invited',
-      });
-      const { user, orgUser } = userData;
-
-      const response = await request(app.getHttpServer()).post('/api/users/accept-invite').send({
-        token: orgUser.invitationToken,
-        password: uuidv4(),
-      });
-
-      expect(response.statusCode).toBe(201);
-
-      const organizationUser = await getManager().findOneOrFail(OrganizationUser, { where: { userId: user.id } });
-      expect(organizationUser.status).toEqual('active');
-    });
-
-    it('should allow users to accept invitation when Multi-Workspace is disabled', async () => {
-      jest.spyOn(mockConfig, 'get').mockImplementation((key: string) => {
-        switch (key) {
-          case 'DISABLE_MULTI_WORKSPACE':
-            return 'true';
-          default:
-            return process.env[key];
-        }
-      });
-      const userData = await createUser(app, {
-        email: 'organizationUser@tooljet.io',
-        status: 'invited',
-      });
-      const { user, orgUser } = userData;
-
-      const response = await request(app.getHttpServer()).post('/api/users/accept-invite').send({
-        token: orgUser.invitationToken,
-        password: uuidv4(),
-      });
-
-      expect(response.statusCode).toBe(201);
-
-      const organizationUser = await getManager().findOneOrFail(OrganizationUser, { where: { userId: user.id } });
-      expect(organizationUser.status).toEqual('active');
-    });
-  });
-
   describe('POST /api/users/avatar', () => {
     it('should allow users to add a avatar', async () => {
       const userData = await createUser(app, { email: 'admin@tooljet.io' });

programming_language: TypeScript