Fix Admin control to disable workspace creation for non-admin users (#14895)

fix #13460

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
Ketan Mehta
2025-10-06 12:41:17 +05:30
committed by GitHub
parent cd4800eb86
commit 66d9a6d7bc
5 changed files with 48 additions and 1 deletions
@@ -490,6 +490,10 @@ export class AuthResolver {
@AuthUser() currentUser: User,
@AuthProvider() authProvider: AuthProviderEnum,
): Promise<SignUpOutput> {
await this.signInUpService.checkWorkspaceCreationIsAllowedOrThrow(
currentUser,
);
const { user, workspace } = await this.signInUpService.signUpOnNewWorkspace(
{ type: 'existingUser', existingUser: currentUser },
);
@@ -355,6 +355,36 @@ export class SignInUpService {
return { canImpersonate: false, canAccessFullAdminPanel: false };
}
private isWorkspaceCreationLimitedToServerAdmins(): boolean {
return this.twentyConfigService.get(
'IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS',
);
}
private async isFirstWorkspaceForUser(userId: string): Promise<boolean> {
const count = await this.userWorkspaceService.countUserWorkspaces(userId);
return count === 0;
}
async checkWorkspaceCreationIsAllowedOrThrow(
currentUser: User,
): Promise<void> {
if (!this.isWorkspaceCreationLimitedToServerAdmins()) return;
if (await this.isFirstWorkspaceForUser(currentUser.id)) return;
if (!currentUser.canAccessFullAdminPanel) {
throw new AuthException(
'Workspace creation is restricted to admins',
AuthExceptionCode.FORBIDDEN_EXCEPTION,
{
userFriendlyMessage: t`Workspace creation is restricted to admins`,
},
);
}
}
async signUpOnNewWorkspace(
userData: ExistingUserOrPartialUserWithPicture['userData'],
) {
@@ -358,6 +358,15 @@ export class ConfigVariables {
})
EMAIL_SMTP_PASSWORD: string;
@ConfigVariablesMetadata({
group: ConfigVariablesGroup.Other,
description:
'When enabled, only server admins can create new workspaces. Ignored during initial setup when no workspace exists.',
type: ConfigVariableType.BOOLEAN,
})
@IsOptional()
IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS = false;
@ConfigVariablesMetadata({
group: ConfigVariablesGroup.StorageConfig,
description: 'Type of storage to use (local or S3)',
@@ -217,6 +217,10 @@ export class UserWorkspaceService extends TypeOrmQueryService<UserWorkspace> {
return workspace;
}
async countUserWorkspaces(userId: string): Promise<number> {
return await this.userWorkspaceRepository.count({ where: { userId } });
}
async findAvailableWorkspacesByEmail(email: string) {
const user = await this.userRepository.findOne({
where: {
@@ -90,7 +90,7 @@ describe('roles permissions', () => {
expect(resp.status).toBe(200);
expect(resp.body.errors).toBeUndefined();
expect(resp.body.data.getRoles).toHaveLength(5);
expect(resp.body.data.getRoles).toHaveLength(7);
const roles = resp.body.data.getRoles;
const guestRole = roles.find((role: any) => role.label === 'Guest');