fix: prevent anonymous users from bypassing workspace creation restriction (#17635)

When `IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS` is true, anonymous
users could still create workspaces because
`checkWorkspaceCreationIsAllowedOrThrow` checked per-user workspace
count (always 0 for new users) instead of system-wide workspace count.

The bootstrap bypass now only applies when no workspaces exist in the
entire system. Also adds the same check in `signUpOnNewWorkspace` to
guard the `signInUp` code path.

Fixes #17631

Generated with [Claude Code](https://claude.ai/claude-code)

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
This commit is contained in:
Félix Malfait
2026-02-04 10:47:24 +01:00
committed by GitHub
parent 29093fdbf1
commit 0edc3a385c
@@ -403,8 +403,8 @@ export class SignInUpService {
);
}
private async isFirstWorkspaceForUser(userId: string): Promise<boolean> {
const count = await this.userWorkspaceService.countUserWorkspaces(userId);
private async isFirstWorkspaceInSystem(): Promise<boolean> {
const count = await this.workspaceRepository.count();
return count === 0;
}
@@ -414,7 +414,8 @@ export class SignInUpService {
): Promise<void> {
if (!this.isWorkspaceCreationLimitedToServerAdmins()) return;
if (await this.isFirstWorkspaceForUser(currentUser.id)) return;
// Only allow bypass during initial system bootstrap (no workspaces exist yet)
if (await this.isFirstWorkspaceInSystem()) return;
if (!currentUser.canAccessFullAdminPanel) {
throw new AuthException(
@@ -445,6 +446,25 @@ export class SignInUpService {
);
}
if (
this.isWorkspaceCreationLimitedToServerAdmins() &&
!(await this.isFirstWorkspaceInSystem())
) {
const isExistingAdmin =
userData.type === 'existingUser' &&
userData.existingUser.canAccessFullAdminPanel;
if (!isExistingAdmin) {
throw new AuthException(
'Workspace creation is restricted to admins',
AuthExceptionCode.FORBIDDEN_EXCEPTION,
{
userFriendlyMessage: msg`Workspace creation is restricted to admins`,
},
);
}
}
const { canImpersonate, canAccessFullAdminPanel } =
await this.setDefaultImpersonateAndAccessFullAdminPanel();