[Workavia] Brand → "Workavia Drive" + multi-tenant Company per email domain

- 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.
This commit is contained in:
stanig2106
2026-05-02 15:38:22 +02:00
parent 03cf4614a5
commit e0347f85f0
22 changed files with 293 additions and 217 deletions
@@ -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 },
}),
);
@@ -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";