diff --git a/packages/cli/commands/start.ts b/packages/cli/commands/start.ts index 079cbc1971acb..726b5a8603030 100644 --- a/packages/cli/commands/start.ts +++ b/packages/cli/commands/start.ts @@ -389,6 +389,12 @@ export class Start extends Command { const editorUrl = GenericHelpers.getBaseUrl(); this.log(`\nEditor is now accessible via:\n${editorUrl}`); + const saveManualExecutions = config.get('executions.saveDataManualExecutions') as boolean; + + if (saveManualExecutions) { + this.log('\nManual executions will be visible only for the owner'); + } + // Allow to open n8n editor by pressing "o" if (Boolean(process.stdout.isTTY) && process.stdin.setRawMode) { process.stdin.setRawMode(true); diff --git a/packages/cli/commands/user-management/reset.ts b/packages/cli/commands/user-management/reset.ts index 53d8fd7976812..f13e0108ce07c 100644 --- a/packages/cli/commands/user-management/reset.ts +++ b/packages/cli/commands/user-management/reset.ts @@ -50,7 +50,10 @@ export class Reset extends Command { await Db.collections.User!.delete({ id: Not(owner.id) }); await Db.collections.User!.save(Object.assign(owner, this.defaultUserProps)); - await Db.collections.Settings!.update({ key: 'userManagement.hasOwner' }, { value: 'false' }); + await Db.collections.Settings!.update( + { key: 'userManagement.isInstanceOwnerSetUp' }, + { value: 'false' }, + ); } catch (error) { console.error('Error resetting database. See log messages for details.'); if (error instanceof Error) logger.error(error.message); diff --git a/packages/cli/src/CredentialsHelper.ts b/packages/cli/src/CredentialsHelper.ts index 530270695ad66..080f93ea562b6 100644 --- a/packages/cli/src/CredentialsHelper.ts +++ b/packages/cli/src/CredentialsHelper.ts @@ -212,32 +212,40 @@ export class CredentialsHelper extends ICredentialsHelper { /** * Returns the credentials instance * - * @param {INodeCredentialsDetails} nodeCredentials id and name to return instance of - * @param {string} type Type of the credentials to return instance of + * @param {INodeCredentialsDetails} nodeCredential id and name to return instance of + * @param {string} type Type of the credential to return instance of * @returns {Credentials} * @memberof CredentialsHelper */ async getCredentials( - nodeCredentials: INodeCredentialsDetails, + nodeCredential: INodeCredentialsDetails, type: string, + userId?: string, ): Promise { - if (!nodeCredentials.id) { - throw new Error(`Credentials "${nodeCredentials.name}" for type "${type}" don't have an ID.`); + if (!nodeCredential.id) { + throw new Error(`Credential "${nodeCredential.name}" of type "${type}" has no ID.`); } - const credentials = await Db.collections.Credentials!.findOne(nodeCredentials.id); + const credential = userId + ? await Db.collections + .SharedCredentials!.findOneOrFail({ + relations: ['credentials'], + where: { credentials: { id: nodeCredential.id, type }, user: { id: userId } }, + }) + .then((shared) => shared.credentials) + : await Db.collections.Credentials!.findOneOrFail({ id: nodeCredential.id, type }); - if (!credentials) { + if (!credential) { throw new Error( - `Credentials with ID "${nodeCredentials.id}" don't exist for type "${type}".`, + `Credential with ID "${nodeCredential.id}" does not exist for type "${type}".`, ); } return new Credentials( - { id: credentials.id.toString(), name: credentials.name }, - credentials.type, - credentials.nodesAccess, - credentials.data, + { id: credential.id.toString(), name: credential.name }, + credential.type, + credential.nodesAccess, + credential.data, ); } diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index d4c313d6d1759..5dcf1c9dfb28e 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -317,7 +317,7 @@ class App { userManagement: { enabled: config.get('userManagement.disabled') === false || - config.get('userManagement.hasOwner') === true, + config.get('userManagement.isInstanceOwnerSetUp') === true, // showSetupOnFirstLoad: config.get('userManagement.disabled') === false, // && config.get('userManagement.skipOwnerSetup') === true smtpSetup: config.get('userManagement.emails.mode') === 'smtp', }, diff --git a/packages/cli/src/UserManagement/UserManagementHelper.ts b/packages/cli/src/UserManagement/UserManagementHelper.ts index e246156f6e02b..a2880b6bbc795 100644 --- a/packages/cli/src/UserManagement/UserManagementHelper.ts +++ b/packages/cli/src/UserManagement/UserManagementHelper.ts @@ -158,6 +158,15 @@ export async function checkPermissionsForExecution( return true; } +/** + * Check if a URL contains an auth-excluded endpoint. + */ +export function isAuthExcluded(url: string, ignoredEndpoints: string[]): boolean { + return !!ignoredEndpoints + .filter(Boolean) // skip empty paths + .find((ignoredEndpoint) => url.includes(ignoredEndpoint)); +} + /** * Check if the endpoint is `POST /users/:id`. */ diff --git a/packages/cli/src/UserManagement/email/UserManagementMailer.ts b/packages/cli/src/UserManagement/email/UserManagementMailer.ts index 6297e3aca316a..8bda8cd95dd98 100644 --- a/packages/cli/src/UserManagement/email/UserManagementMailer.ts +++ b/packages/cli/src/UserManagement/email/UserManagementMailer.ts @@ -1,6 +1,8 @@ +/* eslint-disable import/no-cycle */ import { existsSync, readFileSync } from 'fs'; import { IDataObject } from 'n8n-workflow'; import { join as pathJoin } from 'path'; +import { GenericHelpers } from '../..'; import config = require('../../../config'); import { InviteEmailData, @@ -10,8 +12,11 @@ import { } from './Interfaces'; import { NodeMailer } from './NodeMailer'; -function getTemplate(configKeyName: string, defaultFilename: string) { - const templateOverride = config.get(`userManagement.emails.templates.${configKeyName}`) as string; +async function getTemplate(configKeyName: string, defaultFilename: string) { + const templateOverride = (await GenericHelpers.getConfigValue( + `userManagement.emails.templates.${configKeyName}`, + )) as string; + let template; if (templateOverride && existsSync(templateOverride)) { template = readFileSync(templateOverride, { @@ -46,7 +51,7 @@ export class UserManagementMailer { } async invite(inviteEmailData: InviteEmailData): Promise { - let template = getTemplate('invite', 'invite.html'); + let template = await getTemplate('invite', 'invite.html'); template = replaceStrings(template, inviteEmailData); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -61,7 +66,7 @@ export class UserManagementMailer { } async passwordReset(passwordResetData: PasswordResetData): Promise { - let template = getTemplate('passwordReset', 'passwordReset.html'); + let template = await getTemplate('passwordReset', 'passwordReset.html'); template = replaceStrings(template, passwordResetData); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion diff --git a/packages/cli/src/UserManagement/email/index.ts b/packages/cli/src/UserManagement/email/index.ts index e577648f8e9b2..1d49343ff7840 100644 --- a/packages/cli/src/UserManagement/email/index.ts +++ b/packages/cli/src/UserManagement/email/index.ts @@ -1,3 +1,4 @@ +/* eslint-disable import/no-cycle */ import { getInstance, UserManagementMailer } from './UserManagementMailer'; export { getInstance, UserManagementMailer }; diff --git a/packages/cli/src/UserManagement/routes/index.ts b/packages/cli/src/UserManagement/routes/index.ts index 1a94ac4186f0f..f2caeba5b29b0 100644 --- a/packages/cli/src/UserManagement/routes/index.ts +++ b/packages/cli/src/UserManagement/routes/index.ts @@ -19,7 +19,7 @@ import { usersNamespace } from './users'; import { passwordResetNamespace } from './passwordReset'; import { AuthenticatedRequest } from '../../requests'; import { ownerNamespace } from './owner'; -import { isAuthenticatedRequest, isPostUsersId } from '../UserManagementHelper'; +import { isAuthExcluded, isPostUsersId, isAuthenticatedRequest } from '../UserManagementHelper'; export function addRoutes(this: N8nApp, ignoredEndpoints: string[], restEndpoint: string): void { // needed for testing; not adding overhead since it directly returns if req.cookies exists @@ -65,22 +65,12 @@ export function addRoutes(this: N8nApp, ignoredEndpoints: string[], restEndpoint isPostUsersId(req, restEndpoint) || req.url.startsWith(`/${restEndpoint}/forgot-password`) || req.url.startsWith(`/${restEndpoint}/resolve-password-token`) || - req.url.startsWith(`/${restEndpoint}/change-password`) + req.url.startsWith(`/${restEndpoint}/change-password`) || + isAuthExcluded(req.url, ignoredEndpoints) ) { return next(); } - for (let i = 0; i < ignoredEndpoints.length; i++) { - const path = ignoredEndpoints[i]; - if (!path) { - // Skip empty paths (they might exist) - // eslint-disable-next-line no-continue - continue; - } - if (req.url.includes(path)) { - return next(); - } - } return passport.authenticate('jwt', { session: false })(req, res, next); }); diff --git a/packages/cli/src/UserManagement/routes/owner.ts b/packages/cli/src/UserManagement/routes/owner.ts index 64b16b5ffc37f..b1ea283aa2ca1 100644 --- a/packages/cli/src/UserManagement/routes/owner.ts +++ b/packages/cli/src/UserManagement/routes/owner.ts @@ -17,7 +17,7 @@ import { sanitizeUser, validatePassword } from '../UserManagementHelper'; export function ownerNamespace(this: N8nApp): void { /** * Promote a shell into the owner of the n8n instance, - * and enable `hasOwner` instance setting. + * and enable `isInstanceOwnerSetUp` setting. */ this.app.post( `/${this.restEndpoint}/owner`, @@ -25,7 +25,7 @@ export function ownerNamespace(this: N8nApp): void { const { email, firstName, lastName, password } = req.body; const { id: userId } = req.user; - if (config.get('userManagement.hasOwner')) { + if (config.get('userManagement.isInstanceOwnerSetUp')) { Logger.debug( 'Request to claim instance ownership failed because instance owner already exists', { @@ -79,14 +79,14 @@ export function ownerNamespace(this: N8nApp): void { Logger.info('Owner updated successfully', { userId: req.user.id }); - config.set('userManagement.hasOwner', true); + config.set('userManagement.isInstanceOwnerSetUp', true); await Db.collections.Settings!.update( - { key: 'userManagement.hasOwner' }, + { key: 'userManagement.isInstanceOwnerSetUp' }, { value: JSON.stringify(true) }, ); - Logger.debug('Setting hasOwner updated successfully', { userId: req.user.id }); + Logger.debug('Setting isInstanceOwnerSetUp updated successfully', { userId: req.user.id }); await issueCookie(res, owner); diff --git a/packages/cli/src/UserManagement/routes/users.ts b/packages/cli/src/UserManagement/routes/users.ts index 3c2bc1560e174..a1b97ee8dccb5 100644 --- a/packages/cli/src/UserManagement/routes/users.ts +++ b/packages/cli/src/UserManagement/routes/users.ts @@ -41,7 +41,7 @@ export function usersNamespace(this: N8nApp): void { ); } - if (!config.get('userManagement.hasOwner')) { + if (!config.get('userManagement.isInstanceOwnerSetUp')) { Logger.debug( 'Request to send email invite(s) to user(s) failed because emailing was not set up', ); diff --git a/packages/cli/src/databases/entities/User.ts b/packages/cli/src/databases/entities/User.ts index 40b06464721f7..1e8258c953122 100644 --- a/packages/cli/src/databases/entities/User.ts +++ b/packages/cli/src/databases/entities/User.ts @@ -10,6 +10,7 @@ import { ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn, + AfterLoad, } from 'typeorm'; import { IsEmail, IsString, Length } from 'class-validator'; import config = require('../../../config'); @@ -119,4 +120,14 @@ export class User { setUpdateDate(): void { this.updatedAt = new Date(); } + + /** + * Whether the user is pending setup completion. + */ + isPending: boolean; + + @AfterLoad() + computeIsPending(): void { + this.isPending = !!this.password; + } } diff --git a/packages/cli/src/databases/mysqldb/migrations/1636626154933-CreateUserManagement.ts b/packages/cli/src/databases/mysqldb/migrations/1636626154933-CreateUserManagement.ts index 5df3cd87eb50f..1282f8aa672d2 100644 --- a/packages/cli/src/databases/mysqldb/migrations/1636626154933-CreateUserManagement.ts +++ b/packages/cli/src/databases/mysqldb/migrations/1636626154933-CreateUserManagement.ts @@ -244,7 +244,7 @@ export class CreateUserManagement1636626154933 implements MigrationInterface { 'INSERT INTO `' + tablePrefix + 'settings` (`key`, value, loadOnStartup) values ' + - '("userManagement.hasOwner", "false", 1)', + '("userManagement.isInstanceOwnerSetUp", "false", 1)', ); } diff --git a/packages/cli/src/databases/mysqldb/migrations/1644424784709-AddExecutionEntityIndexes.ts b/packages/cli/src/databases/mysqldb/migrations/1644424784709-AddExecutionEntityIndexes.ts index 045c3c047db7c..7f7ce7285fb4b 100644 --- a/packages/cli/src/databases/mysqldb/migrations/1644424784709-AddExecutionEntityIndexes.ts +++ b/packages/cli/src/databases/mysqldb/migrations/1644424784709-AddExecutionEntityIndexes.ts @@ -1,16 +1,10 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; import * as config from '../../../../config'; -import { isTestRun } from '../../../../test/integration/shared/utils'; export class AddExecutionEntityIndexes1644424784709 implements MigrationInterface { name = 'AddExecutionEntityIndexes1644424784709'; public async up(queryRunner: QueryRunner): Promise { - !isTestRun && - console.log( - '\n\nINFO: Started migration for execution entity indexes.\n Depending on the number of saved executions, it may take a while.\n\n', - ); - const tablePrefix = config.get('database.tablePrefix'); await queryRunner.query( diff --git a/packages/cli/src/databases/postgresdb/migrations/1636626154934-CreateUserManagement.ts b/packages/cli/src/databases/postgresdb/migrations/1636626154934-CreateUserManagement.ts index d8cc484325f5d..f65a8ce1139e8 100644 --- a/packages/cli/src/databases/postgresdb/migrations/1636626154934-CreateUserManagement.ts +++ b/packages/cli/src/databases/postgresdb/migrations/1636626154934-CreateUserManagement.ts @@ -230,7 +230,7 @@ export class CreateUserManagement1636626154934 implements MigrationInterface { 'INSERT INTO "' + tablePrefix + 'settings" ("key", "value", "loadOnStartup") values ' + - "('userManagement.hasOwner', 'false', true)", + "('userManagement.isInstanceOwnerSetUp', 'false', true)", ); } diff --git a/packages/cli/src/databases/postgresdb/migrations/1644422880309-AddExecutionEntityIndexes.ts b/packages/cli/src/databases/postgresdb/migrations/1644422880309-AddExecutionEntityIndexes.ts index 3e6811e06eaf1..3f910f24707c9 100644 --- a/packages/cli/src/databases/postgresdb/migrations/1644422880309-AddExecutionEntityIndexes.ts +++ b/packages/cli/src/databases/postgresdb/migrations/1644422880309-AddExecutionEntityIndexes.ts @@ -1,15 +1,10 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; import * as config from '../../../../config'; -import { isTestRun } from '../../../../test/integration/shared/utils'; export class AddExecutionEntityIndexes1644422880309 implements MigrationInterface { name = 'AddExecutionEntityIndexes1644422880309'; public async up(queryRunner: QueryRunner): Promise { - !isTestRun && - console.log( - '\n\nINFO: Started migration for execution entity indexes.\n Depending on the number of saved executions, it may take a while.\n\n', - ); let tablePrefix = config.get('database.tablePrefix'); const tablePrefixPure = tablePrefix; diff --git a/packages/cli/src/databases/sqlite/migrations/1636626154932-CreateUserManagement.ts b/packages/cli/src/databases/sqlite/migrations/1636626154932-CreateUserManagement.ts index 04f3a1cb9a92f..f2ffd1c611672 100644 --- a/packages/cli/src/databases/sqlite/migrations/1636626154932-CreateUserManagement.ts +++ b/packages/cli/src/databases/sqlite/migrations/1636626154932-CreateUserManagement.ts @@ -95,7 +95,7 @@ export class CreateUserManagement1636626154932 implements MigrationInterface { await queryRunner.query(` INSERT INTO "${tablePrefix}settings" (key, value, loadOnStartup) values - ('userManagement.hasOwner', 'false', true) + ('userManagement.isInstanceOwnerSetUp', 'false', true) `); } diff --git a/packages/cli/src/databases/sqlite/migrations/1644421939510-AddExecutionEntityIndexes.ts b/packages/cli/src/databases/sqlite/migrations/1644421939510-AddExecutionEntityIndexes.ts index f8b72537d39fd..17c20e5fd7147 100644 --- a/packages/cli/src/databases/sqlite/migrations/1644421939510-AddExecutionEntityIndexes.ts +++ b/packages/cli/src/databases/sqlite/migrations/1644421939510-AddExecutionEntityIndexes.ts @@ -1,16 +1,10 @@ import { MigrationInterface, QueryRunner } from 'typeorm'; import * as config from '../../../../config'; -import { isTestRun } from '../../../../test/integration/shared/utils'; export class AddExecutionEntityIndexes1644421939510 implements MigrationInterface { name = 'AddExecutionEntityIndexes1644421939510'; public async up(queryRunner: QueryRunner): Promise { - !isTestRun && - console.log( - '\n\nINFO: Started migration for execution entity indexes.\n Depending on the number of saved executions, it may take a while.\n\n', - ); - const tablePrefix = config.get('database.tablePrefix'); await queryRunner.query(`DROP INDEX IF EXISTS 'IDX_${tablePrefix}ca4a71b47f28ac6ea88293a8e2'`); diff --git a/packages/cli/test/integration/auth.endpoints.test.ts b/packages/cli/test/integration/auth.endpoints.test.ts index eba9a3ef51bc3..b9d7ea8b308b1 100644 --- a/packages/cli/test/integration/auth.endpoints.test.ts +++ b/packages/cli/test/integration/auth.endpoints.test.ts @@ -35,10 +35,10 @@ beforeEach(async () => { role: globalOwnerRole, }); - config.set('userManagement.hasOwner', true); + config.set('userManagement.isInstanceOwnerSetUp', true); await Db.collections.Settings!.update( - { key: 'userManagement.hasOwner' }, + { key: 'userManagement.isInstanceOwnerSetUp' }, { value: JSON.stringify(true) }, ); }); diff --git a/packages/cli/test/integration/me.endpoints.test.ts b/packages/cli/test/integration/me.endpoints.test.ts index a12f0528f1a51..639c815bbbb1d 100644 --- a/packages/cli/test/integration/me.endpoints.test.ts +++ b/packages/cli/test/integration/me.endpoints.test.ts @@ -203,10 +203,10 @@ describe('Member', () => { await Db.collections.User!.save(newMember); - config.set('userManagement.hasOwner', true); + config.set('userManagement.isInstanceOwnerSetUp', true); await Db.collections.Settings!.update( - { key: 'userManagement.hasOwner' }, + { key: 'userManagement.isInstanceOwnerSetUp' }, { value: JSON.stringify(true) }, ); }); @@ -367,9 +367,10 @@ describe('Owner', () => { globalRole: globalOwnerRole, }); - config.set('userManagement.hasOwner', true); + config.set('userManagement.isInstanceOwnerSetUp', true); }); + afterEach(async () => { await utils.truncate(['User']); }); diff --git a/packages/cli/test/integration/owner.endpoints.test.ts b/packages/cli/test/integration/owner.endpoints.test.ts index 46cfb0a6c22fe..ce4725912eb2f 100644 --- a/packages/cli/test/integration/owner.endpoints.test.ts +++ b/packages/cli/test/integration/owner.endpoints.test.ts @@ -33,7 +33,7 @@ afterAll(() => { return getConnection().close(); }); -test('POST /owner should create owner and enable hasOwner setting', async () => { +test('POST /owner should create owner and enable isInstanceOwnerSetUp', async () => { const owner = await Db.collections.User!.findOneOrFail(); const authOwnerAgent = await utils.createAgent(app, { auth: true, user: owner }); @@ -68,11 +68,11 @@ test('POST /owner should create owner and enable hasOwner setting', async () => expect(storedOwner.firstName).toBe(TEST_USER.firstName); expect(storedOwner.lastName).toBe(TEST_USER.lastName); - const hasOwnerConfig = config.get('userManagement.hasOwner'); - expect(hasOwnerConfig).toBe(true); + const isInstanceOwnerSetUpConfig = config.get('userManagement.isInstanceOwnerSetUp'); + expect(isInstanceOwnerSetUpConfig).toBe(true); - const hasOwnerSetting = await utils.getHasOwnerSetting(); - expect(hasOwnerSetting).toBe(true); + const isInstanceOwnerSetUpSetting = await utils.isInstanceOwnerSetUp(); + expect(isInstanceOwnerSetUpSetting).toBe(true); }); test('POST /owner should fail with invalid inputs', async () => { diff --git a/packages/cli/test/integration/passwordReset.endpoints.test.ts b/packages/cli/test/integration/passwordReset.endpoints.test.ts index 7adbfd94a9d75..12fadbc398d6a 100644 --- a/packages/cli/test/integration/passwordReset.endpoints.test.ts +++ b/packages/cli/test/integration/passwordReset.endpoints.test.ts @@ -34,7 +34,7 @@ beforeEach(async () => { jest.mock('../../config'); }); - config.set('userManagement.hasOwner', true); + config.set('userManagement.isInstanceOwnerSetUp', true); config.set('userManagement.emails.mode', ''); await utils.createUser({ diff --git a/packages/cli/test/integration/shared/utils.ts b/packages/cli/test/integration/shared/utils.ts index 635365e89918e..6e7f49e57115f 100644 --- a/packages/cli/test/integration/shared/utils.ts +++ b/packages/cli/test/integration/shared/utils.ts @@ -68,7 +68,7 @@ export function initTestServer({ testServer.app.use(bodyParser.urlencoded({ extended: true })); config.set('userManagement.jwtSecret', 'My JWT secret'); - config.set('userManagement.hasOwner', false); + config.set('userManagement.isInstanceOwnerSetUp', false); if (applyAuth) { authMiddleware.apply(testServer, [AUTHLESS_ENDPOINTS, REST_PATH_SEGMENT]); @@ -312,9 +312,9 @@ export function getAuthToken(response: request.Response, authCookieName = AUTH_C // settings // ---------------------------------- -export async function getHasOwnerSetting() { +export async function isInstanceOwnerSetUp() { const { value } = await Db.collections.Settings!.findOneOrFail({ - key: 'userManagement.hasOwner', + key: 'userManagement.isInstanceOwnerSetUp', }); return Boolean(value); diff --git a/packages/cli/test/integration/users.endpoints.test.ts b/packages/cli/test/integration/users.endpoints.test.ts index 5ec5a66e8f6c4..d6a785a542f60 100644 --- a/packages/cli/test/integration/users.endpoints.test.ts +++ b/packages/cli/test/integration/users.endpoints.test.ts @@ -61,7 +61,7 @@ beforeEach(async () => { role: globalOwnerRole, }); - config.set('userManagement.hasOwner', true); + config.set('userManagement.isInstanceOwnerSetUp', true); config.set('userManagement.emails.mode', ''); // @ts-ignore hack because config doesn't change for helper UMHelper.isEmailSetUp = false; diff --git a/packages/editor-ui/src/Interface.ts b/packages/editor-ui/src/Interface.ts index b3575c73d0f40..3a52fafd757ef 100644 --- a/packages/editor-ui/src/Interface.ts +++ b/packages/editor-ui/src/Interface.ts @@ -793,6 +793,7 @@ export interface IUserResponse { id: string; }; personalizationAnswers?: IPersonalizationSurveyAnswers | null; + isPending: boolean; } export interface IInviteResponse { diff --git a/packages/editor-ui/src/modules/users.ts b/packages/editor-ui/src/modules/users.ts index c5e9164245ff2..766979ea73344 100644 --- a/packages/editor-ui/src/modules/users.ts +++ b/packages/editor-ui/src/modules/users.ts @@ -13,9 +13,9 @@ import { } from '../Interface'; import { getPersonalizedNodeTypes, isAuthorized, PERMISSIONS, ROLE } from './userHelpers'; -const isDefaultUser = (user: IUserResponse | null) => Boolean(user && !user.email && user.globalRole && user.globalRole.name === ROLE.Owner); +const isDefaultUser = (user: IUserResponse | null) => Boolean(user && user.isPending && user.globalRole && user.globalRole.name === ROLE.Owner); -const isPendingUser = (user: IUserResponse | null) => Boolean(user && user.email && !user.firstName && !user.lastName); +const isPendingUser = (user: IUserResponse | null) => Boolean(user && user.isPending); const module: Module = {