From 6a926a7ea7dfca603d39998812c2d34bfd380524 Mon Sep 17 00:00:00 2001 From: Andrey Sobolev Date: Mon, 3 Mar 2025 01:11:25 +0700 Subject: [PATCH] UBERF-9540: Fix invite message and add rate limit (#8123) Signed-off-by: Andrey Sobolev --- plugins/setting-assets/lang/cs.json | 3 +- plugins/setting-assets/lang/de.json | 3 +- plugins/setting-assets/lang/en.json | 3 +- plugins/setting-assets/lang/es.json | 3 +- plugins/setting-assets/lang/fr.json | 3 +- plugins/setting-assets/lang/it.json | 3 +- plugins/setting-assets/lang/pt.json | 3 +- plugins/setting-assets/lang/ru.json | 3 +- plugins/setting-assets/lang/zh.json | 3 +- .../src/components/General.svelte | 12 ++++-- plugins/setting-resources/src/plugin.ts | 3 +- server/account/src/operations.ts | 40 +++++++++++++++++++ server/account/src/utils.ts | 2 +- 13 files changed, 70 insertions(+), 14 deletions(-) diff --git a/plugins/setting-assets/lang/cs.json b/plugins/setting-assets/lang/cs.json index 4d02898b88..ca3608606f 100644 --- a/plugins/setting-assets/lang/cs.json +++ b/plugins/setting-assets/lang/cs.json @@ -135,6 +135,7 @@ "StartOfTheWeek": "Začátek týdne", "SystemSetupString": "Nastavení systému ({day})", "DefaultString": "Výchozí ({day})", - "AddAttribute": "Přidat atribut" + "AddAttribute": "Přidat atribut", + "WorkspaceNamePattern": "Název musí být 40 znaků nebo méně, není prázdný a nesmí obsahovat speciální znaky (<, >, /)" } } \ No newline at end of file diff --git a/plugins/setting-assets/lang/de.json b/plugins/setting-assets/lang/de.json index 76bc10e969..17cf2075bc 100644 --- a/plugins/setting-assets/lang/de.json +++ b/plugins/setting-assets/lang/de.json @@ -135,6 +135,7 @@ "StartOfTheWeek": "Wochenstart", "SystemSetupString": "Systemkonfiguration ({day})", "DefaultString": "Stillschweigend ({day})", - "AddAttribute": "Attribut hinzufügen" + "AddAttribute": "Attribut hinzufügen", + "WorkspaceNamePattern": "Name muss 40 Zeichen oder weniger sein, nicht leer sein und keine Sonderzeichen (<, >, /) enthalten" } } diff --git a/plugins/setting-assets/lang/en.json b/plugins/setting-assets/lang/en.json index f0b2210ee3..1beba24b49 100644 --- a/plugins/setting-assets/lang/en.json +++ b/plugins/setting-assets/lang/en.json @@ -135,6 +135,7 @@ "StartOfTheWeek": "Start of the week", "SystemSetupString": "System Setup ({day})", "DefaultString": "Default ({day})", - "AddAttribute": "Add attribute" + "AddAttribute": "Add attribute", + "WorkspaceNamePattern": "Name must be 40 characters or less, not empty, and cannot contain special characters (<, >, /)" } } diff --git a/plugins/setting-assets/lang/es.json b/plugins/setting-assets/lang/es.json index 22c0ae3123..44453f3565 100644 --- a/plugins/setting-assets/lang/es.json +++ b/plugins/setting-assets/lang/es.json @@ -126,6 +126,7 @@ "StartOfTheWeek": "Inicio de la semana", "SystemSetupString": "Configuración del sistema ({day})", "DefaultString": "Predeterminado ({day})", - "AddAttribute": "Añadir atributo" + "AddAttribute": "Añadir atributo", + "WorkspaceNamePattern": "El nombre debe tener 40 caracteres o menos, no esté vacío y no puede contener caracteres especiales (<, >, /)" } } \ No newline at end of file diff --git a/plugins/setting-assets/lang/fr.json b/plugins/setting-assets/lang/fr.json index be70c25cef..c0146a1d7f 100644 --- a/plugins/setting-assets/lang/fr.json +++ b/plugins/setting-assets/lang/fr.json @@ -135,6 +135,7 @@ "StartOfTheWeek": "Début de semaine", "SystemSetupString": "Configuration du système ({day})", "DefaultString": "Par défaut ({day})", - "AddAttribute": "Ajouter un attribut" + "AddAttribute": "Ajouter un attribut", + "WorkspaceNamePattern": "Le nom doit comporter 40 caractères ou moins, ne doit pas être vide et ne doit pas contenir de caractères spéciaux (<, >, /)" } } \ No newline at end of file diff --git a/plugins/setting-assets/lang/it.json b/plugins/setting-assets/lang/it.json index d123614bb0..5362dede46 100644 --- a/plugins/setting-assets/lang/it.json +++ b/plugins/setting-assets/lang/it.json @@ -135,6 +135,7 @@ "StartOfTheWeek": "Inizio settimana", "SystemSetupString": "Configurazione del sistema ({day})", "DefaultString": "Predefinito ({day})", - "AddAttribute": "Aggiungi attributo" + "AddAttribute": "Aggiungi attributo", + "WorkspaceNamePattern": "Il nome deve essere di 40 caratteri o meno, non vuoto e non può contenere caratteri speciali (<, >, /)" } } diff --git a/plugins/setting-assets/lang/pt.json b/plugins/setting-assets/lang/pt.json index 18cd70c58e..75d03ff4ab 100644 --- a/plugins/setting-assets/lang/pt.json +++ b/plugins/setting-assets/lang/pt.json @@ -126,6 +126,7 @@ "StartOfTheWeek": "Início da semana", "SystemSetupString": "Configuração do sistema ({day})", "DefaultString": "Predefinição ({day})", - "AddAttribute": "Adicionar atributo" + "AddAttribute": "Adicionar atributo", + "WorkspaceNamePattern": "O nome deve ter 40 caracteres ou menos, não está vazio e não pode conter caracteres especiais (<, >, /)" } } \ No newline at end of file diff --git a/plugins/setting-assets/lang/ru.json b/plugins/setting-assets/lang/ru.json index 70ae8b4660..37d97d8ec0 100644 --- a/plugins/setting-assets/lang/ru.json +++ b/plugins/setting-assets/lang/ru.json @@ -136,6 +136,7 @@ "StartOfTheWeek": "Начало недели", "SystemSetupString": "Системная настройка ({day})", "DefaultString": "По умолчанию ({day})", - "AddAttribute": "Добавить атрибут" + "AddAttribute": "Добавить атрибут", + "WorkspaceNamePattern": "Имя должно быть длиной 40 символов или меньше, не должно быть пустым и не может содержать специальные символы (<, >, /)" } } diff --git a/plugins/setting-assets/lang/zh.json b/plugins/setting-assets/lang/zh.json index 7a7780a0f4..c89da658ad 100644 --- a/plugins/setting-assets/lang/zh.json +++ b/plugins/setting-assets/lang/zh.json @@ -135,6 +135,7 @@ "StartOfTheWeek": "本周开始", "SystemSetupString": "系统设置({day})", "DefaultString": "违约({day})", - "AddAttribute": "添加属性" + "AddAttribute": "添加属性", + "WorkspaceNamePattern": "名称必须为 40 个字符或更少,不能为空,不能包含特殊字符(<、>、/)" } } diff --git a/plugins/setting-resources/src/components/General.svelte b/plugins/setting-resources/src/components/General.svelte index e4fb66b54f..8718662f20 100644 --- a/plugins/setting-resources/src/components/General.svelte +++ b/plugins/setting-resources/src/components/General.svelte @@ -52,6 +52,14 @@ let name: string = '' const accountClient = getAccountClient() + const disabledSet = ['\n', '<', '>', '/', '\\'] + + $: editNameDisabled = + isEditingName && + (name.trim().length > 40 || + name.trim() === oldName || + name.trim() === '' || + disabledSet.some((it) => name.includes(it))) void loadWorkspaceName() @@ -80,8 +88,6 @@ isEditingName = false } - $: editNameDisabled = isEditingName && (name.trim() === oldName || name.trim() === '') - async function handleDelete (): Promise { showPopup(MessageBox, { label: setting.string.DeleteWorkspace, @@ -99,7 +105,7 @@ let workspaceSettings: WorkspaceSetting | undefined = undefined const client = getClient() - client.findOne(setting.class.WorkspaceSetting, {}).then((r) => { + void client.findOne(setting.class.WorkspaceSetting, {}).then((r) => { workspaceSettings = r }) diff --git a/plugins/setting-resources/src/plugin.ts b/plugins/setting-resources/src/plugin.ts index c1f4f9be9d..0466d41a1a 100644 --- a/plugins/setting-resources/src/plugin.ts +++ b/plugins/setting-resources/src/plugin.ts @@ -117,6 +117,7 @@ export default mergeIds(settingId, setting, { Calendar: '' as IntlString, StartOfTheWeek: '' as IntlString, SystemSetupString: '' as IntlString, - DefaultString: '' as IntlString + DefaultString: '' as IntlString, + WorkspaceNamePattern: '' as IntlString } }) diff --git a/server/account/src/operations.ts b/server/account/src/operations.ts index 0133b9809d..f0f65b2214 100644 --- a/server/account/src/operations.ts +++ b/server/account/src/operations.ts @@ -342,6 +342,8 @@ export async function createWorkspace ( const { workspaceName, region } = params const { account } = decodeTokenVerbose(ctx, token) + checkRateLimit(account, workspaceName) + ctx.info('Creating workspace record', { workspaceName, account, region }) // Any confirmed social ID will do @@ -410,6 +412,12 @@ export async function createInviteLink ( }) } +// TODO: Temporary solution to prevent spam using sendInvite +const invitesSend = new Map() + export async function sendInvite ( ctx: MeasureContext, db: AccountDB, @@ -433,6 +441,8 @@ export async function sendInvite ( throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUuid })) } + checkRateLimit(account, workspaceUuid) + const expHours = 48 const exp = expHours * 60 * 60 * 1000 @@ -444,6 +454,34 @@ export async function sendInvite ( ctx.info('Invite has been sent', { to: inviteEmail.to, workspaceUuid: workspace.uuid, workspaceName: workspace.name }) } +function checkRateLimit (email: string, workspaceName: string): void { + const now = Date.now() + const lastInvites = invitesSend.get(email) + if (lastInvites !== undefined) { + lastInvites.totalSend++ + lastInvites.lastSend = now + if (lastInvites.totalSend > 5 && (now - lastInvites.lastSend) < 60 * 1000) { + // Less 60 seconds between invites + throw new PlatformError( + new Status(Severity.ERROR, platform.status.WorkspaceRateLimit, { workspace: workspaceName }) + ) + } + invitesSend.delete(email) + } else { + invitesSend.set(email, { + lastSend: now, + totalSend: 1 + }) + } + + // We need to cleanup map + for (const [k, vv] of invitesSend.entries()) { + if (vv.lastSend < now - 60 * 1000) { + invitesSend.delete(k) + } + } +} + export async function resendInvite ( ctx: MeasureContext, db: AccountDB, @@ -463,6 +501,8 @@ export async function resendInvite ( throw new PlatformError(new Status(Severity.ERROR, platform.status.WorkspaceNotFound, { workspaceUuid })) } + checkRateLimit(account, workspaceUuid) + const expHours = 48 const newExp = Date.now() + expHours * 60 * 60 * 1000 diff --git a/server/account/src/utils.ts b/server/account/src/utils.ts index 7b029e0f38..78817e4348 100644 --- a/server/account/src/utils.ts +++ b/server/account/src/utils.ts @@ -1193,7 +1193,7 @@ export async function getInviteEmail ( ): Promise { const front = getFrontUrl(branding) const link = concatLink(front, `/login/join?inviteId=${inviteId}`) - const ws = workspace.name !== '' ? workspace.name : workspace.url + const ws = (workspace.name !== '' ? workspace.name : workspace.url).replace(/[<>/]/g, '').slice(0, 40) const lang = branding?.language return {