fix: domínio SIP único por tenant + grupo de captura + revelar senha do ramal
Achado real, detalhado pelo usuário testando o PABX de verdade: TenantsController
.create() gravava telephonyDomain="b2bcall.local" fixo pra TODO tenant novo —
b2bcall-fs-config decide qual tenant é dono de um REGISTER só pelo domínio
(Tenant.findFirst({telephonyDomain})), então com todo tenant no mesmo domínio o
isolamento de PABX (ramais, call groups, filas, IVR) não tinha como funcionar de
verdade. Investigado direto no container antes de mudar qualquer coisa: o sofia
profile "internal" (vanilla) já vem com <domain name="all" alias="true".../> — o
FreeSWITCH sempre aceitou domínio dinâmico por REGISTER, o bug era só a aplicação.
Nenhuma mudança de infra foi necessária.
Tenant.telephonyDomain agora é obrigatório e @unique (migration com backfill:
tenants existentes ganharam {code}.b2bcall.net, e os Extension.domain já criados
foram atualizados junto). Tela de criação de tenant sugere {code}.b2bcall.net ao
digitar o código, editável.
Extension.callGroup (novo) — ramais no mesmo grupo podem capturar a chamada um do
outro (*8), fora do grupo não. Vira a variable call-group no directory XML; a regra
de dialplan do *8 em si fica pra configurar em Telefonia > Dialplan (editor já
existe). Editável na criação e depois (PATCH /extensions/:id, novo).
POST /extensions/:id/reveal-password (novo) — achado real: "show once" puro não
funciona no dia a dia (reconfigurar um telefone/softphone precisa da senha de novo;
forçar reset toda vez derruba outro aparelho já configurado). sipPasswordEnc sempre
foi criptografia reversível, nunca hash — só não estava exposto. Auditado
(EXTENSION_PASSWORD_REVEALED) por ser sensível mesmo sem escrita.
apps/freeswitch-config reconstruído e reiniciado. Testado ponta a ponta contra o
container REAL via docker exec: senha revelada bate com a gerada na criação,
call-group aparece no XML, e o mesmo número de ramal em domínios diferentes nunca
se confunde (isolamento cross-tenant confirmado de verdade).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
49
TODO.md
49
TODO.md
@@ -1828,6 +1828,55 @@ usuário: `Ctrl+F5` depois de um tempo logado quebrava com 401 cru)
|
||||
mas o error boundary do client detecta, desloga e redireciona pro
|
||||
login sozinho — sem crash na tela pro usuário
|
||||
|
||||
## PHASE 52 — Domínio SIP por tenant + grupo de captura + revelar senha
|
||||
(achado real detalhado pelo usuário, testando o PABX de verdade)
|
||||
- [x] achado real (o mais sério dos três): `TenantsController.create()`
|
||||
gravava `telephonyDomain: "b2bcall.local"` fixo pra TODO tenant
|
||||
novo — `b2bcall-fs-config` decide qual tenant é dono de um REGISTER
|
||||
só pelo domínio (`Tenant.findFirst({ telephonyDomain: domain })`),
|
||||
então com todo tenant no mesmo domínio o isolamento de PABX (ramais,
|
||||
call groups, filas, IVR) não tinha como funcionar de verdade.
|
||||
Investigado direto no container: o sofia profile `internal`
|
||||
(vanilla, nunca sobrescrito) já vem com
|
||||
`<domain name="all" alias="true" parse="false"/>` — o FreeSWITCH
|
||||
SEMPRE aceitou domínio dinâmico por REGISTER, o bug era só a
|
||||
aplicação gravando o mesmo valor pra todo mundo. Nenhuma mudança de
|
||||
infra (Dockerfile/sofia profile) foi necessária
|
||||
- [x] `Tenant.telephonyDomain` agora é obrigatório e `@unique` (migration
|
||||
`20260830140000_tenant_domain_and_call_group`, com backfill:
|
||||
tenants existentes ganharam `{code}.b2bcall.net` automaticamente,
|
||||
e os `Extension.domain` já criados foram atualizados junto — sem
|
||||
isso ficariam com um `<domain>` desatualizado no XML). Tela de
|
||||
criação de tenant sugere `{code}.b2bcall.net` ao digitar o código,
|
||||
editável antes de criar. Duplicado vira 403 claro
|
||||
- [x] `Extension.callGroup` (novo, nullable) — grupo de captura (secao
|
||||
178): ramais no mesmo grupo podem atender a chamada um do outro
|
||||
(`*8`/group pickup), ramais fora do grupo não. Vira a variable
|
||||
`call-group` no directory XML (`packages/telephony`); a extensão de
|
||||
dialplan do `*8` em si é uma regra a configurar em Telefonia >
|
||||
Dialplan (já existe o editor), não precisava de código novo — só do
|
||||
dado. Editável na criação e depois (`PATCH /extensions/:id`, novo)
|
||||
- [x] `POST /extensions/:id/reveal-password` (novo) — achado real: "show
|
||||
once" puro não funciona no dia a dia de um PABX (reconfigurar um
|
||||
telefone físico ou softphone precisa da senha de novo; forçar reset
|
||||
toda vez derruba o registro de qualquer aparelho já configurado
|
||||
com a senha antiga). `sipPasswordEnc` sempre foi criptografia
|
||||
reversível (AES-256-GCM), nunca hash — só não estava exposto.
|
||||
Diferente de `reset-password`: nunca troca nada, só decifra a
|
||||
atual. Auditado (`EXTENSION_PASSWORD_REVEALED`) por ser sensível
|
||||
mesmo sem escrita
|
||||
- [x] Tela de criação de ramal ganhou um bloco "Configuração do telefone/
|
||||
softphone" (domínio, ramal, proxy=servidor, senha) — os 4 campos
|
||||
exatos que o usuário descreveu precisar pra configurar um aparelho
|
||||
- [x] `apps/freeswitch-config` reconstruído e reiniciado (mudança de
|
||||
schema + lógica) — testado ponta a ponta contra o container REAL:
|
||||
criado tenant com domínio próprio + ramal com callGroup, `docker
|
||||
exec` no FreeSWITCH chamando `fs-config` direto (mesma rede
|
||||
Docker) confirmou (1) senha revelada bate exatamente com a gerada
|
||||
na criação, (2) `call-group` aparece no XML, (3) o mesmo número de
|
||||
ramal em domínios diferentes NUNCA se confunde (isolamento
|
||||
cross-tenant confirmado de verdade, não só por leitura de código)
|
||||
|
||||
---
|
||||
|
||||
## Riscos conhecidos
|
||||
|
||||
Reference in New Issue
Block a user