diff --git a/dev/docker-compose.yaml b/dev/docker-compose.yaml index e253dbfeb0..01217906c4 100644 --- a/dev/docker-compose.yaml +++ b/dev/docker-compose.yaml @@ -85,6 +85,7 @@ services: environment: - ACCOUNT_PORT=3000 - SERVER_SECRET=secret + - WORKSPACE_LIMIT_PER_USER=10000 - STATS_URL=http://host.docker.internal:4900 # - DB_URL=postgresql://postgres:example@postgres:5432 - DB_URL=${MONGO_URL} 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 54f70b1452..b918502c60 100644 --- a/packages/platform/lang/en.json +++ b/packages/platform/lang/en.json @@ -19,6 +19,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 5a3af6c907..50cf449c6e 100644 --- a/packages/platform/src/platform.ts +++ b/packages/platform/src/platform.ts @@ -156,6 +156,7 @@ export default plugin(platformId, { AccountAlreadyConfirmed: '' as StatusCode<{ account: string }>, 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 61e56429e1..12032d3cfb 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 19c9027562..68790a7029 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/plugins/login-resources/src/components/CreateWorkspaceForm.svelte b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte index 7de9226630..be631b3901 100644 --- a/plugins/login-resources/src/components/CreateWorkspaceForm.svelte +++ b/plugins/login-resources/src/components/CreateWorkspaceForm.svelte @@ -17,7 +17,7 @@ import { OK, Severity, Status, getEmbeddedLabel } from '@hcengineering/platform' import { LoginInfo } from '@hcengineering/login' - import { ButtonMenu, DropdownLabels, getCurrentLocation, navigate } from '@hcengineering/ui' + import { ButtonMenu, getCurrentLocation, navigate } from '@hcengineering/ui' import { workbenchId } from '@hcengineering/workbench' import { onMount } from 'svelte' import login from '../plugin' diff --git a/qms-tests/docker-compose.yaml b/qms-tests/docker-compose.yaml index 17b784b9ff..2e4d1f460b 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://host.docker.internal:3334 - STORAGE_CONFIG=${STORAGE_CONFIG} diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index d7004d2578..dcb69c96fb 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -94,6 +94,10 @@ import { } from './utils' import MD5 from 'crypto-js/md5' + +const workspaceLimitPerUser = + process.env.WORKSPACE_LIMIT_PER_USER != null ? parseInt(process.env.WORKSPACE_LIMIT_PER_USER) : 10 + function buildGravatarId (email: string): string { return MD5(email.trim().toLowerCase()).toString() } @@ -1566,6 +1570,16 @@ export async function createUserWorkspace ( throw new PlatformError(new Status(Severity.ERROR, platform.status.AccountNotConfirmed, { account: email })) } + // Get a list of created workspaces + const created = (await db.workspace.find({ createdBy: email })).length + + if (created >= (userAccount.workspaceLimit ?? workspaceLimitPerUser)) { + ctx.warn('created-by-limit', { email, workspace: workspaceName, limit: userAccount.workspaceLimit }) + throw new PlatformError( + new Status(Severity.ERROR, platform.status.WorkspaceLimitReached, { workspace: workspaceName }) + ) + } + if (userAccount.lastWorkspace !== undefined && userAccount.admin === false) { if (Date.now() - userAccount.lastWorkspace < 60 * 1000) { throw new PlatformError( diff --git a/server/account/src/types.ts b/server/account/src/types.ts index 6f95ab85ec..6460abd2c6 100644 --- a/server/account/src/types.ts +++ b/server/account/src/types.ts @@ -49,6 +49,8 @@ export interface Account { githubId?: string githubUser?: string openId?: string + + workspaceLimit?: number } /** diff --git a/tests/docker-compose.yaml b/tests/docker-compose.yaml index 0cfc98e922..871762673e 100644 --- a/tests/docker-compose.yaml +++ b/tests/docker-compose.yaml @@ -58,6 +58,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 22321ba443..6f8250b588 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