Commit Graph

10 Commits

Author SHA1 Message Date
d2ea83c06a feat: activate mod_xml_curl with b2bcall-fs-config (agente.md secao 26)
- apps/freeswitch-config (b2bcall-fs-config): Fastify service implementing
  the mod_xml_curl HTTP protocol (form-encoded POST -> XML response),
  containerized, no host port published
- reactivated mod_xml_curl in FreeSWITCH, binding restricted to
  directory|dialplan only (configuration was removed after testing showed
  it firing several unnecessary HTTP round-trips at boot for module
  configs we don't need dynamic — matches agente.md's own 'don't put every
  critical config through XML Curl' guidance)
- no extensions/dialplan tables exist yet (next phases), so the service
  always answers 'not found' for now — this phase only proves the wire
  protocol works without breaking the static vanilla config fallback
- verified end-to-end: user/8888 (nowhere) -> SUBSCRIBER_ABSENT via
  fs-config; user/1000 (static vanilla extension) -> USER_NOT_REGISTERED,
  proving FreeSWITCH correctly falls through to static XML when xml_curl
  says not found
- docs/XML_CURL.md, including the not-yet-authenticated endpoint note (fine
  while it only returns not-found; needs gateway-credentials before serving
  real directory/dialplan data)
2026-08-28 07:07:05 -03:00
60e9f6838e feat: add Event Socket integration (b2bcall-fs-events)
- packages/telephony: TelephonyProvider interface (agente.md secao 25) and
  FreeSwitchTelephonyProvider implementation over the 'esl' library
  (actively maintained, TypeScript-native, built-in reconnect-with-backoff
  satisfying secao 195); normalizeEslEvent() translates raw ESL events into
  the internal vocabulary (secao 24)
- apps/freeswitch-events (b2bcall-fs-events): permanent ESL connection,
  resubscribes on every reconnect, publishes normalized events to the
  'b2bcall:events' Redis pub/sub channel; containerized (Dockerfile +
  docker-compose service) since its whole job is reaching the freeswitch
  container by internal hostname
- packages/shared: reusable createLogger() (structured JSON per secao 189),
  fixed a BigInt serialization crash surfaced by the esl library's error
  stats
- found and fixed a real FreeSWITCH 1.11 default: without an explicit
  apply-inbound-acl, mod_event_socket silently rejects any non-loopback
  connection ('Access Denied, go away.') even with the correct password —
  added a dedicated ACL (loopback + the Docker Compose network range, never
  0.0.0.0/0) in infrastructure/freeswitch/overrides/autoload_configs/
- verified end-to-end with a local loopback test call: CALL_CREATED ->
  CALL_ANSWERED -> CALL_ENDED observed on the Redis channel with the
  correct callUuid and hangup cause
- docs/EVENT_SOCKET.md
2026-08-28 06:47:28 -03:00
b3b0aaacb3 feat: add FreeSWITCH service (SignalWire packages, not compiled from source)
- infrastructure/freeswitch/Dockerfile: debian:trixie-slim + SignalWire
  packaged freeswitch-meta-vanilla, avoiding a C/C++ build on a 1.9GB RAM VM
- FREESWITCH_PAT used only via Docker BuildKit secret, apt credentials file
  created and deleted within the same RUN — verified absent from the final
  image with docker history
- minimal module set (agente.md secao 15): sofia, event_socket, commands,
  dptools, callcenter, avmd, curl, local_stream, etc. mod_xml_curl installed
  but disabled — it refuses to load without a configured gateway-url, which
  will exist once b2bcall-fs-config is built
- entrypoint.sh rotates the Event Socket password away from the 'ClueCon'
  default at container runtime (never baked into the image); fails loudly if
  ESL_PASSWORD is unset
- port 8021 not published to the host; only reachable from other containers
  on the compose network
- found and fixed: freeswitch-conf-vanilla is a Recommends (not a Depends)
  of freeswitch-meta-vanilla, so --no-install-recommends silently produced
  an empty /etc/freeswitch and a crash loop
- verified end-to-end: fs_cli status via ESL with the custom password,
  default password rejected, expected modules loaded, healthcheck green,
  ~44MB RAM usage
- docs/FREESWITCH.md, docs/NETWORK_ARCHITECTURE.md (network_mode decision
  deferred until a real SIP trunk exists)
2026-08-28 06:30:25 -03:00
68b403a7ff feat: add apps/api (NestJS + Fastify) with authentication endpoints
- POST /auth/login, /auth/refresh, /auth/logout, /auth/select-tenant,
  /auth/change-password, GET /auth/tenants — wired to packages/auth
- JwtAuthGuard + DomainExceptionFilter (401/403 without leaking internals)
- LoginRateLimitGuard: Redis-backed 5/min per IP and per email (agente.md
  secao 149), safe across multiple API instances
- helmet + restrictive cors (deny-by-default) + global rate limit
- GET /health, /health/live, /health/ready checking Postgres and Redis
- changePassword() added to packages/auth for the mustChangePassword flow
- fixed REDIS_HOST/POSTGRES_HOST docker-compose-only hostnames not
  resolving from the host process; added REDIS_URL for host-side use
- verified end-to-end with curl: login, wrong password / unknown email
  (same generic error), authenticated route, missing token, refresh
  rotation, logout revocation, and the 429 rate limit kicking in after 5
  attempts
2026-08-28 06:12:34 -03:00
70c5586595 feat: implement authentication and RBAC
- packages/auth: Argon2id password hashing, JWT access tokens (jose),
  opaque refresh tokens with rotation, generic error messages (no
  user-enumeration via timing or message differences)
- roles/permissions/role_permissions/user_roles/sessions/audit_logs schema
  (agente.md secoes 142-150); RBAC scope PLATFORM vs TENANT
- withUserContext(): narrow RLS exception so a user can discover their own
  tenant_memberships before a tenant is chosen (login flow)
- userHasPermission()/isPlatformUser(): explicit service-layer RBAC checks
  (roles/permissions tables are not RLS-protected — documented why in
  docs/AUTHENTICATION.md)
- seed: permission catalog, 4 system roles, initial Platform Super Admin
  (password written once to FIRST_LOGIN.txt, 600, outside Git)
- automated end-to-end test: login, RBAC check, refresh rotation, logout
2026-08-28 05:58:49 -03:00
d66170c795 feat: implement tenant isolation with PostgreSQL RLS
- users + tenant_memberships tables (tenant-scoped)
- RLS policy on tenant_memberships using set_config('app.current_tenant_id', ...)
- withTenantContext() helper for transaction-scoped tenant context
- separate non-superuser app role (b2bcall_app): the default Docker postgres
  user is SUPERUSER and always bypasses RLS even with FORCE, so the app must
  never connect through the migration/owner role. Documented in
  docs/TENANT_ISOLATION.md.
- automated isolation test proving tenant A never sees tenant B's data
2026-08-28 05:47:23 -03:00
c0f29328bd docs: update TODO with phase 02 progress 2026-08-27 23:22:55 -03:00
d1addb0a31 feat: add typescript monorepo with pnpm workspaces and prisma database package
- root package.json + pnpm-workspace.yaml + tsconfig.base.json
- packages/types: canonical enums (TenantStatus, AgentState)
- packages/shared: re-exports types, base for future utilities
- packages/database: Prisma 7 schema with driver adapter (pg), initial
  tenants table matching agente.md section 29, first migration applied
2026-08-27 23:22:38 -03:00
2f130622d1 feat: bootstrap b2bcall saas architecture
- monorepo skeleton (apps/, packages/, infrastructure/, scripts/, docs/)
- docker-compose with PostgreSQL 18 and Redis 7 (localhost-only)
- .gitignore and .env.example
- initial TODO.md and docs/ARCHITECTURE.md
2026-08-27 23:12:29 -03:00
e79ad6e1be chore: initial commit 2026-08-27 23:04:15 -03:00