From 633e3d8fa18ef478fc97bc4a6fad89dab31ae37a Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Tue, 11 Mar 2025 21:00:06 +0700 Subject: [PATCH] UBERF-9606: Limit a number of workspaces per user (#8192) (#8199) --- dev/docker-compose.yaml | 1 + dev/prod/webpack.config.js | 2 +- packages/platform/lang/cs.json | 3 ++- packages/platform/lang/de.json | 3 ++- packages/platform/lang/en.json | 3 ++- packages/platform/lang/es.json | 3 ++- packages/platform/lang/fr.json | 3 ++- packages/platform/lang/it.json | 3 ++- packages/platform/lang/pt.json | 3 ++- packages/platform/lang/ru.json | 3 ++- packages/platform/lang/zh.json | 3 ++- packages/platform/src/platform.ts | 1 + plugins/login-assets/lang/en.json | 2 +- plugins/login-assets/lang/es.json | 2 +- qms-tests/docker-compose.yaml | 1 + server/account/src/operations.ts | 23 ++++++++++++++++++----- tests/docker-compose.yaml | 1 + ws-tests/docker-compose.yaml | 1 + 18 files changed, 44 insertions(+), 17 deletions(-) diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index 9d92be7dd7..01314d576a 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -90,6 +90,7 @@ services: - SERVER_SECRET=secret - ADMIN_EMAILS=admin - STATS_URL=http://huly.local:4900 + - WORKSPACE_LIMIT_PER_USER=10000 # - DB_URL=postgresql://root@huly.local:26257/defaultdb?sslmode=disable - DB_URL=${MONGO_URL} # - DB_NS=account-2 diff --git a/dev/prod/webpack.config.js b/dev/prod/webpack.config.js index 78036c9ee6..e9c7b58f69 100644 --- a/dev/prod/webpack.config.js +++ b/dev/prod/webpack.config.js @@ -74,7 +74,7 @@ const devProxy = { const devProxyTest = { '/account': { - target: 'http://localhost:3003', + target: 'http://huly.local:3003', changeOrigin: true, pathRewrite: { '^/account': '' }, logLevel: 'debug' diff --git a/packages/platform/lang/cs.json b/packages/platform/lang/cs.json index 483ce656bc..363c124a71 100644 --- a/packages/platform/lang/cs.json +++ b/packages/platform/lang/cs.json @@ -19,6 +19,7 @@ "AccountAlreadyConfirmed": "Účet již byl potvrzen", "WorkspaceAlreadyExists": "Pracovní prostor již existuje", "InvalidOtp": "Neplatný kód", - "InviteNotFound": "Pozvánka s email:{email} nebyla nalezena." + "InviteNotFound": "Pozvánka s email:{email} nebyla nalezena.", + "WorkspaceLimitReached": "Dosáhli jste limitu pracovních prostorů. Kontaktujte nás..." } } diff --git a/packages/platform/lang/de.json b/packages/platform/lang/de.json index f1a55b1b7b..c022b1625e 100644 --- a/packages/platform/lang/de.json +++ b/packages/platform/lang/de.json @@ -19,6 +19,7 @@ "AccountAlreadyConfirmed": "Konto wurde bereits bestätigt", "WorkspaceAlreadyExists": "Arbeitsbereich existiert bereits", "InvalidOtp": "Ungültiger Code", - "InviteNotFound": "Einladung mit E-Mail: {email} nicht gefunden." + "InviteNotFound": "Einladung mit E-Mail: {email} nicht gefunden.", + "WorkspaceLimitReached": "Sie haben das Arbeitsbereichslimit erreicht. Bitte kontaktieren Sie uns..." } } diff --git a/packages/platform/lang/en.json b/packages/platform/lang/en.json index 40a13583ca..792425a6db 100644 --- a/packages/platform/lang/en.json +++ b/packages/platform/lang/en.json @@ -18,6 +18,7 @@ "AccountAlreadyConfirmed": "Account already confirmed", "WorkspaceAlreadyExists": "Workspace already exists", "InvalidOtp": "Invalid code", - "InviteNotFound": "Invitation with email:{email} not found." + "InviteNotFound": "Invitation with email:{email} not found.", + "WorkspaceLimitReached": "You have reached the workspace limit. Please contact us..." } } diff --git a/packages/platform/lang/es.json b/packages/platform/lang/es.json index 44357b9b3b..bb93099627 100644 --- a/packages/platform/lang/es.json +++ b/packages/platform/lang/es.json @@ -19,6 +19,7 @@ "AccountAlreadyConfirmed": "La cuenta ya está confirmada", "WorkspaceAlreadyExists": "El espacio de trabajo ya existe", "InvalidOtp": "Código no válido", - "InviteNotFound": "No se encontró la invitación con email:{email}." + "InviteNotFound": "No se encontró la invitación con email:{email}.", + "WorkspaceLimitReached": "Ha alcanzado el límite de espacios de trabajo. Póngase en contacto con nosotros..." } } diff --git a/packages/platform/lang/fr.json b/packages/platform/lang/fr.json index 1fbb731065..f5bfa2fdce 100644 --- a/packages/platform/lang/fr.json +++ b/packages/platform/lang/fr.json @@ -19,6 +19,7 @@ "AccountAlreadyConfirmed": "Compte déjà confirmé", "WorkspaceAlreadyExists": "L'espace de travail existe déjà", "InvalidOtp": "Code invalide", - "InviteNotFound": "Invitation avec l'email:{email} introuvable." + "InviteNotFound": "Invitation avec l'email:{email} introuvable.", + "WorkspaceLimitReached": "Vous avez atteint la limite d'espace de travail. Veuillez contacter nous..." } } diff --git a/packages/platform/lang/it.json b/packages/platform/lang/it.json index 47f5c786b7..cdac6d3c5a 100644 --- a/packages/platform/lang/it.json +++ b/packages/platform/lang/it.json @@ -19,6 +19,7 @@ "AccountAlreadyConfirmed": "Account già confermato", "WorkspaceAlreadyExists": "Spazio di lavoro già esistente", "InvalidOtp": "Codice non valido", - "InviteNotFound": "Invito con email:{email} non trovato." + "InviteNotFound": "Invito con email:{email} non trovato.", + "WorkspaceLimitReached": "Hai raggiunto il limite di spazi di lavoro. Contattaci..." } } diff --git a/packages/platform/lang/pt.json b/packages/platform/lang/pt.json index 09b944eb15..3ff0935f4e 100644 --- a/packages/platform/lang/pt.json +++ b/packages/platform/lang/pt.json @@ -19,6 +19,7 @@ "AccountAlreadyConfirmed": "Conta já confirmada", "WorkspaceAlreadyExists": "Espaço de trabalho já existe", "InvalidOtp": "Código inválido", - "InviteNotFound": "Convite com email:{email} não encontrado." + "InviteNotFound": "Convite com email:{email} não encontrado.", + "WorkspaceLimitReached": "Você atingiu o limite de espaço de trabalho. Entre em contato conosco..." } } diff --git a/packages/platform/lang/ru.json b/packages/platform/lang/ru.json index 3a6f7c4010..9cd6a70332 100644 --- a/packages/platform/lang/ru.json +++ b/packages/platform/lang/ru.json @@ -19,6 +19,7 @@ "AccountAlreadyConfirmed": "Аккаунт уже подтвержден", "WorkspaceAlreadyExists": "Рабочее пространство уже существует", "InvalidOtp": "Неверный код", - "InviteNotFound": "Приглашение с email:{email} не найдено." + "InviteNotFound": "Приглашение с email:{email} не найдено.", + "WorkspaceLimitReached": "Вы достигли лимита рабочих пространств. Свяжитесь с нами..." } } diff --git a/packages/platform/lang/zh.json b/packages/platform/lang/zh.json index 9f1327e23e..29ac527dc0 100644 --- a/packages/platform/lang/zh.json +++ b/packages/platform/lang/zh.json @@ -19,6 +19,7 @@ "AccountAlreadyConfirmed": "账户已确认", "WorkspaceAlreadyExists": "工作区已存在", "InvalidOtp": "无效的代码", - "InviteNotFound": "未找到 id 为 {email} 的邀请。" + "InviteNotFound": "未找到 id 为 {email} 的邀请。", + "WorkspaceLimitReached": "您已达到工作区限制。请联系我们..." } } diff --git a/packages/platform/src/platform.ts b/packages/platform/src/platform.ts index af593f5467..5313604b3d 100644 --- a/packages/platform/src/platform.ts +++ b/packages/platform/src/platform.ts @@ -159,6 +159,7 @@ export default plugin(platformId, { AccountAlreadyExists: '' as StatusCode, WorkspaceAlreadyExists: '' as StatusCode<{ workspace: string }>, WorkspaceRateLimit: '' as StatusCode<{ workspace: string }>, + WorkspaceLimitReached: '' as StatusCode<{ workspace: string }>, InvalidOtp: '' as StatusCode, InviteNotFound: '' as StatusCode<{ email: string }> }, diff --git a/plugins/login-assets/lang/en.json b/plugins/login-assets/lang/en.json index 9da4ce69cc..5cc122a20b 100644 --- a/plugins/login-assets/lang/en.json +++ b/plugins/login-assets/lang/en.json @@ -3,7 +3,7 @@ "RequiredField": "Required field {field}", "FieldsDoNotMatch": "{field} don't match {field2}", "ConnectingToServer": "Connecting to server....", - "IncorrectValue": "Incorrect value {field}" + "IncorrectValue": "Incorrect value {field}" }, "string": { "LogIn": "Log In", diff --git a/plugins/login-assets/lang/es.json b/plugins/login-assets/lang/es.json index ed7701d8e9..b14a2158f3 100644 --- a/plugins/login-assets/lang/es.json +++ b/plugins/login-assets/lang/es.json @@ -3,7 +3,7 @@ "RequiredField": "Campo obligatorio: {field}", "FieldsDoNotMatch": "{field} no coincide con {field2}", "ConnectingToServer": "Conectando al servidor...", - "IncorrectValue": "Valor incorrecto para {field}" + "IncorrectValue": "Valor incorrecto para {field}" }, "string": { "LogIn": "Iniciar sesión", diff --git a/qms-tests/docker-compose.yaml b/qms-tests/docker-compose.yaml index 14b26d58a3..2585789f1a 100644 --- a/qms-tests/docker-compose.yaml +++ b/qms-tests/docker-compose.yaml @@ -53,6 +53,7 @@ services: environment: - ACCOUNT_PORT=3003 - SERVER_SECRET=secret + - WORKSPACE_LIMIT_PER_USER=100 - DB_URL=mongodb://mongodb:27018 - TRANSACTOR_URL=ws://transactor:3334;ws://huly.local:3334 - STORAGE_CONFIG=${STORAGE_CONFIG} diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index d474d8b341..5e1c2ba580 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -101,6 +101,9 @@ import { // Move to config? const processingTimeoutMs = 30 * 1000 +const workspaceLimitPerUser = + process.env.WORKSPACE_LIMIT_PER_USER != null ? parseInt(process.env.WORKSPACE_LIMIT_PER_USER) : 10 + /* =================================== */ /* ============OPERATIONS============= */ /* =================================== */ @@ -353,6 +356,21 @@ export async function createWorkspace ( if (socialId == null) { throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotConfirmed, {})) } + const person = await db.person.findOne({ uuid: socialId.personUuid }) + if (person == null) { + throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {})) + } + + // Get a list of created workspaces + const created = (await db.workspace.find({ createdBy: socialId.personUuid })).length + + // TODO: Add support for per person limit increase + if (created >= workspaceLimitPerUser) { + ctx.warn('created-by-limit', { person: socialId.key, workspace: workspaceName }) + throw new PlatformError( + new Status(Severity.ERROR, platform.status.WorkspaceLimitReached, { workspace: workspaceName }) + ) + } const { workspaceUuid, workspaceUrl } = await createWorkspaceRecord(ctx, db, branding, workspaceName, account, region) @@ -360,11 +378,6 @@ export async function createWorkspace ( ctx.info('Creating workspace record done', { workspaceName, region, account: socialId.personUuid }) - const person = await db.person.findOne({ uuid: socialId.personUuid }) - if (person == null) { - throw new PlatformError(new Status(Severity.ERROR, platform.status.InternalServerError, {})) - } - return { account, socialId: socialId.key, diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml index 85ff38005f..d98bb64d2b 100644 --- a/tests/docker-compose.yaml +++ b/tests/docker-compose.yaml @@ -52,6 +52,7 @@ services: environment: - ACCOUNT_PORT=3003 - SERVER_SECRET=secret + - WORKSPACE_LIMIT_PER_USER=100 - DB_URL=mongodb://mongodb:27018 - TRANSACTOR_URL=ws://transactor:3334;ws://localhost:3334 - STORAGE_CONFIG=${STORAGE_CONFIG} diff --git a/ws-tests/docker-compose.yaml b/ws-tests/docker-compose.yaml index 4e2621ff4d..38c212a579 100644 --- a/ws-tests/docker-compose.yaml +++ b/ws-tests/docker-compose.yaml @@ -63,6 +63,7 @@ services: - REGION_INFO=|America;europe| # Europe without name will not be available for creation of new workspaces. # - REGION_INFO=|America;europe|Europe - ADMIN_EMAILS=admin + - WORKSPACE_LIMIT_PER_USER=100 - ACCOUNT_PORT=3003 - SERVER_SECRET=secret - DB_URL=mongodb://huly.local:27018