feat: implement Extensions with real FreeSWITCH directory integration
- extensions table (tenant-scoped, RLS): number, sip_password_enc
(AES-256-GCM via packages/shared/src/crypto.ts), caller_id, context,
sofia_profile, codecs, max_registrations
- apps/api/src/extensions: CRUD (POST/GET/GET:id/DELETE), protected by a
new generic PermissionGuard (@RequirePermission decorator), tenant
resolved only from the JWT (never trusted from the client)
- SIP password is returned in plaintext only once, in the create response;
toPublicExtension() explicitly destructures the encrypted field out
(not a spread) so it can't leak by accident
- b2bcall-fs-config now resolves real directory data: Tenant.telephonyDomain
-> Extension.number, decrypts the password, builds proper directory XML
including a dial-string param (missing it caused originate to fail with
MANDATORY_IE_MISSING instead of the expected USER_NOT_REGISTERED)
- pinned FreeSWITCH's 357737{domain} to a stable value (b2bcall.local) via a
vars.xml patch in the Dockerfile -- it previously used the container's
dynamic IP, which could never match a stored telephony_domain
- added HTTP Basic auth between FreeSWITCH and fs-config
(gateway-credentials, timingSafeEqual comparison) now that the service
returns real secret data, closing the gap flagged as pending in the XML
Curl phase instead of leaving it open
- found and fixed: PermissionGuard's constructor-injected Reflector came
back undefined at runtime under tsx/esbuild (unreliable cross-file
decorator metadata emission) -- fixed with an explicit @Inject(Reflector);
worth watching for in future guards/services run via tsx
- verified end-to-end: create extension -> originate user/<ext> reports
USER_NOT_REGISTERED (found, not registered) -> delete -> back to
SUBSCRIBER_ABSENT (not found); password never reappears in any GET;
unauthenticated fs-config requests get 401
- docs/EXTENSIONS.md
This commit is contained in:
@@ -46,6 +46,15 @@ COPY overrides/autoload_configs/modules.conf.xml /etc/freeswitch/autoload_config
|
||||
COPY overrides/autoload_configs/event_socket.conf.xml /etc/freeswitch/autoload_configs/event_socket.conf.xml
|
||||
COPY overrides/autoload_configs/acl.conf.xml /etc/freeswitch/autoload_configs/acl.conf.xml
|
||||
COPY overrides/autoload_configs/xml_curl.conf.xml /etc/freeswitch/autoload_configs/xml_curl.conf.xml
|
||||
|
||||
# Pino $${domain} num valor estavel em vez do IP dinamico do container
|
||||
# (vars.xml vanilla usa "domain=$${local_ip_v4}", que muda a cada restart e
|
||||
# nunca bateria com Tenant.telephonyDomain). Ver docs/EXTENSIONS.md.
|
||||
ARG DEFAULT_SIP_DOMAIN=b2bcall.local
|
||||
RUN sed -i "s/data=\"domain=\$\${local_ip_v4}\"/data=\"domain=${DEFAULT_SIP_DOMAIN}\"/" \
|
||||
/etc/freeswitch/vars.xml \
|
||||
&& grep -q "domain=${DEFAULT_SIP_DOMAIN}" /etc/freeswitch/vars.xml
|
||||
|
||||
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/entrypoint.sh
|
||||
|
||||
|
||||
@@ -6,8 +6,15 @@
|
||||
set -eu
|
||||
|
||||
: "${ESL_PASSWORD:?ESL_PASSWORD precisa estar definido no ambiente do container}"
|
||||
: "${FS_CONFIG_USER:?FS_CONFIG_USER precisa estar definido no ambiente do container}"
|
||||
: "${FS_CONFIG_PASSWORD:?FS_CONFIG_PASSWORD precisa estar definido no ambiente do container}"
|
||||
|
||||
sed -i "s/__ESL_PASSWORD__/${ESL_PASSWORD}/" \
|
||||
/etc/freeswitch/autoload_configs/event_socket.conf.xml
|
||||
|
||||
sed -i \
|
||||
-e "s/__FS_CONFIG_USER__/${FS_CONFIG_USER}/" \
|
||||
-e "s/__FS_CONFIG_PASSWORD__/${FS_CONFIG_PASSWORD}/" \
|
||||
/etc/freeswitch/autoload_configs/xml_curl.conf.xml
|
||||
|
||||
exec "$@"
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
<configuration name="xml_curl.conf" description="cURL XML Gateway">
|
||||
<bindings>
|
||||
<binding name="b2bcall-fs-config">
|
||||
<!-- b2bcall-fs-config ainda so responde "not found" pra tudo (nao
|
||||
existe extensions/dialplan persistidos ainda — fases seguintes).
|
||||
A config estatica vanilla continua valendo como fallback. -->
|
||||
<!-- b2bcall-fs-config responde directory de verdade (extensions) e
|
||||
"not found" pra dialplan ainda (fase seguinte). A config estatica
|
||||
vanilla continua valendo como fallback quando "not found".
|
||||
Credenciais substituidas em runtime pelo entrypoint.sh — nunca
|
||||
ficam de verdade na imagem (mesmo padrao do ESL_PASSWORD). -->
|
||||
<param name="gateway-url" value="http://fs-config:8080/" bindings="directory|dialplan"/>
|
||||
<param name="gateway-credentials" value="__FS_CONFIG_USER__:__FS_CONFIG_PASSWORD__"/>
|
||||
<param name="auth-scheme" value="basic"/>
|
||||
<param name="timeout" value="5"/>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
||||
Reference in New Issue
Block a user