Make subdomain minimum length configurable via env var (#23209)

## What

Introduces a `SUBDOMAIN_MIN_LENGTH` environment variable (default `3`)
controlling the minimum number of characters allowed for a workspace
subdomain.

Until now the minimum was hardcoded (`3`), baked into the shared
`SUBDOMAIN_PATTERN` regex.

## How

- Added the `SUBDOMAIN_MIN_LENGTH` config variable (default `3`) in
`config-variables.ts`.
- Relaxed `SUBDOMAIN_PATTERN` in `twenty-shared` to validate format and
max length only, so the minimum length policy now lives with the caller
instead of being embedded in the regex.
- `isSubdomainValid` now takes a `minLength` argument (defaulting to
`3`) and enforces it explicitly.
- `SubdomainManagerService` reads `SUBDOMAIN_MIN_LENGTH` from config and
passes it to every validation call, making the server the authoritative
source.

## Scope

Server-side only. The frontend validation schema keeps its default
`.min(3)` UX check and is unchanged; the server remains the source of
truth for what subdomains are accepted.

## Tests

- Updated the shared `isValidTwentySubdomain` tests to reflect that the
pattern no longer enforces a minimum length.
- Added tests for the configurable minimum in
`is-subdomain-valid.util.spec.ts`.
- Updated the service spec config mock to return a numeric value for
`SUBDOMAIN_MIN_LENGTH`.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23209?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Marie
2026-08-06 13:03:18 +02:00
committed by GitHub
parent d4c3759c70
commit d8b494d530
26 changed files with 303 additions and 177 deletions
@@ -16,7 +16,10 @@ describe('isValidTwentySubdomain', () => {
expect(isValidTwentySubdomain('a-b-c-d-e')).toBe(true);
});
it('should accept minimum length subdomains (3 characters)', () => {
it('should accept short subdomains regardless of minimum length', () => {
// Minimum length is enforced by the caller, not by the pattern
expect(isValidTwentySubdomain('a')).toBe(true);
expect(isValidTwentySubdomain('ab')).toBe(true);
expect(isValidTwentySubdomain('abc')).toBe(true);
expect(isValidTwentySubdomain('a1b')).toBe(true);
expect(isValidTwentySubdomain('a-b')).toBe(true);
@@ -41,11 +44,6 @@ describe('isValidTwentySubdomain', () => {
expect(isValidTwentySubdomain('')).toBe(false);
});
it('should reject subdomains shorter than 3 characters', () => {
expect(isValidTwentySubdomain('a')).toBe(false);
expect(isValidTwentySubdomain('ab')).toBe(false);
});
it('should reject subdomains longer than 30 characters', () => {
const tooLong = 'a'.repeat(31);