From e0347f85f0a34a841cb6a822c32b55623660ac32 Mon Sep 17 00:00:00 2001 From: stanig2106 Date: Sat, 2 May 2026 15:38:22 +0200 Subject: [PATCH] =?UTF-8?q?[Workavia]=20Brand=20=E2=86=92=20"Workavia=20Dr?= =?UTF-8?q?ive"=20+=20multi-tenant=20Company=20per=20email=20domain?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace "Twake Drive" → "Workavia Drive" in static (index.html, manifest.json), all 16 frontend locales, and shared.tsx. - Patch console/clients/remote.ts (OIDC remote) and console/web/controller.ts (local signup) to derive a per-tenant Company from the user's email domain via uuidv5, replacing the upstream all-zero hardcode. --- .../src/services/console/clients/remote.ts | 39 ++++++++++++++++--- .../src/services/console/web/controller.ts | 32 +++++++++++---- tdrive/frontend/public/index.html | 2 +- tdrive/frontend/public/locales/de.json | 24 ++++++------ tdrive/frontend/public/locales/en.json | 28 ++++++------- tdrive/frontend/public/locales/eo.json | 26 ++++++------- tdrive/frontend/public/locales/es.json | 26 ++++++------- tdrive/frontend/public/locales/eu.json | 26 ++++++------- tdrive/frontend/public/locales/fi.json | 26 ++++++------- tdrive/frontend/public/locales/fr.json | 26 ++++++------- tdrive/frontend/public/locales/it.json | 26 ++++++------- tdrive/frontend/public/locales/ja.json | 26 ++++++------- tdrive/frontend/public/locales/nb_NO.json | 26 ++++++------- tdrive/frontend/public/locales/pt.json | 26 ++++++------- tdrive/frontend/public/locales/ru.json | 26 ++++++------- tdrive/frontend/public/locales/si.json | 8 ++-- tdrive/frontend/public/locales/tr.json | 26 ++++++------- tdrive/frontend/public/locales/vi.json | 28 ++++++------- tdrive/frontend/public/locales/zh_Hans.json | 24 ++++++------ tdrive/frontend/public/manifest.json | 4 +- .../app/views/client/body/drive/shared.tsx | 2 +- tdrive/frontend/yarn.lock | 33 ++++++++++++++++ 22 files changed, 293 insertions(+), 217 deletions(-) diff --git a/tdrive/backend/node/src/services/console/clients/remote.ts b/tdrive/backend/node/src/services/console/clients/remote.ts index 5014413d..22c8ff2d 100644 --- a/tdrive/backend/node/src/services/console/clients/remote.ts +++ b/tdrive/backend/node/src/services/console/clients/remote.ts @@ -23,6 +23,32 @@ import { ConsoleServiceImpl } from "../service"; import coalesce from "../../../utils/coalesce"; import config from "config"; import { CompanyUserRole } from "src/services/user/web/types"; +import { v5 as uuidv5 } from "uuid"; + +// [Workavia] Multi-tenant company resolution. +// +// Upstream tdrive hardcodes every OIDC user into the all-zero Company +// UUID. That breaks Workavia's tenant isolation: users from different +// customer domains (e.g. meconsult.fr, acme.com) would all land in the +// same Company and could see each other's "Shared with me" results, +// search each other in the share-with-user picker, etc. +// +// We derive a deterministic Company UUID per email domain via uuidv5 +// over a fixed namespace, and use the bare domain as the Company's +// human name. First user from a domain bootstraps the Company; every +// subsequent user from that domain joins it. +// +// Namespace UUID is stable & arbitrary — picked once and committed. +const WORKAVIA_NAMESPACE = "1b1c8c44-3a5a-4e2c-bfa1-d3e2f1a0c0ff"; + +function workaviaCompanyForEmail(email: string): { id: string; name: string } { + // Default fallback for malformed emails — keeps behaviour identical + // to upstream (one shared Company) for the broken case so we don't + // break the system on a typo. + const at = email.lastIndexOf("@"); + const domain = at >= 0 ? email.slice(at + 1).toLowerCase() : "tdrive.local"; + return { id: uuidv5(domain, WORKAVIA_NAMESPACE), name: domain }; +} import Session from "../entities/session"; export class ConsoleRemoteClient implements ConsoleServiceClient { version: "1"; @@ -166,15 +192,16 @@ export class ConsoleRemoteClient implements ConsoleServiceClient { await gr.services.users.save(user); - //For now TDrive works with only one company as we don't get it from the SSO - let company = await gr.services.companies.getCompany({ - id: "00000000-0000-4000-0000-000000000000", - }); + // [Workavia] Resolve the Company from the user's email domain so + // each tenant of a Workavia install gets its own isolated Company. + // Upstream behaviour was a single shared Company. + const tenant = workaviaCompanyForEmail(userDTO.email); + let company = await gr.services.companies.getCompany({ id: tenant.id }); if (!company) { company = await gr.services.companies.createCompany( getCompanyInstance({ - id: "00000000-0000-4000-0000-000000000000", - name: "Tdrive", + id: tenant.id, + name: tenant.name, plan: { name: "Local", limits: undefined, features: undefined }, }), ); diff --git a/tdrive/backend/node/src/services/console/web/controller.ts b/tdrive/backend/node/src/services/console/web/controller.ts index 703149f2..b0c1e7a1 100644 --- a/tdrive/backend/node/src/services/console/web/controller.ts +++ b/tdrive/backend/node/src/services/console/web/controller.ts @@ -23,6 +23,18 @@ import { getInstance as getCompanyInstance } from "../../../services/user/entiti import Workspace from "../../../services/workspaces/entities/workspace"; import gr from "../../global-resolver"; import { Configuration } from "../../../core/platform/framework"; +import { v5 as uuidv5 } from "uuid"; + +// [Workavia] Same multi-tenant logic as in console/clients/remote.ts — +// derive the Company from the email domain instead of grouping every +// user into a single Company. +const WORKAVIA_NAMESPACE = "1b1c8c44-3a5a-4e2c-bfa1-d3e2f1a0c0ff"; + +function workaviaCompanyForEmail(email: string): { id: string; name: string } { + const at = email.lastIndexOf("@"); + const domain = at >= 0 ? email.slice(at + 1).toLowerCase() : "tdrive.local"; + return { id: uuidv5(domain, WORKAVIA_NAMESPACE), name: domain }; +} import { CompanyUserRole } from "src/services/user/web/types"; import config from "config"; @@ -96,15 +108,19 @@ export class ConsoleController { const user = await gr.services.users.create(newUser); await gr.services.users.setPassword({ id: user.entity.id }, request.body.password); - //Create a global company for all users in local mode - const companies = await gr.services.companies.getCompanies(); - let company = companies.getEntities()?.[0]; + // [Workavia] Resolve the Company from the user's email domain + // (same logic as the OIDC/remote path). Each tenant gets its + // own isolated Company; first signup of a domain bootstraps it. + const tenant = workaviaCompanyForEmail(email); + let company = await gr.services.companies.getCompany({ id: tenant.id }); if (!company) { - const newCompany = getCompanyInstance({ - name: "Tdrive", - plan: { name: "Local", limits: undefined, features: undefined }, - }); - company = await gr.services.companies.createCompany(newCompany); + company = await gr.services.companies.createCompany( + getCompanyInstance({ + id: tenant.id, + name: tenant.name, + plan: { name: "Local", limits: undefined, features: undefined }, + }), + ); } if (this.rootAdmins.includes(email)) { userRole = "admin"; diff --git a/tdrive/frontend/public/index.html b/tdrive/frontend/public/index.html index cd2c63d1..85702f44 100644 --- a/tdrive/frontend/public/index.html +++ b/tdrive/frontend/public/index.html @@ -2,7 +2,7 @@ - Twake Drive + Workavia Drive { )} - Twake Drive + Workavia Drive
diff --git a/tdrive/frontend/yarn.lock b/tdrive/frontend/yarn.lock index 9964361e..aac0d9b6 100644 --- a/tdrive/frontend/yarn.lock +++ b/tdrive/frontend/yarn.lock @@ -7637,6 +7637,13 @@ binary-extensions@^2.0.0: resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== +bindings@^1.5.0: + version "1.5.0" + resolved "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz" + integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== + dependencies: + file-uri-to-path "1.0.0" + blob@0.0.5: version "0.0.5" resolved "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz" @@ -11801,6 +11808,27 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== +fsevents@^1.0.0: + version "1.2.13" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +fsevents@^1.2.7: + version "1.2.13" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz" + integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== + dependencies: + bindings "^1.5.0" + nan "^2.12.1" + +fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + fullcalendar@^5.1.0: version "5.11.3" resolved "https://registry.npmjs.org/fullcalendar/-/fullcalendar-5.11.3.tgz" @@ -16204,6 +16232,11 @@ multicast-dns@^7.2.5: dns-packet "^5.2.2" thunky "^1.0.2" +nan@^2.12.1: + version "2.17.0" + resolved "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz" + integrity sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ== + nanoid@^3.3.1, nanoid@^3.3.4: version "3.3.4" resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz"