127 Commits
Author SHA1 Message Date
iamdoubz 34f443385d release: version 0.0.13
ci / check (push) Successful in 1m5s
ci / images (push) Successful in 22s
ci / check (pull_request) Successful in 1m5s
ci / images (pull_request) Successful in 4s
2026-10-29 18:40:30 -05:00
iamdoubz 1979a02827 voipms: IP restriction on the setup checklist, getCDR checked against voip.ms's examples 2026-10-29 18:35:06 -05:00
iamdoubz 08bc611dec security: fraud alerts (M11-T10), admin access spec (M11-T9) 2026-10-29 18:35:06 -05:00
iamdoubz 63286a1954 asterisk: AMI limited to the classes we use, no shell-running modules in the image 2026-10-29 18:35:06 -05:00
iamdoubz 80e97b4118 release: version 0.0.12
ci / check (push) Successful in 1m25s
ci / images (push) Successful in 2m35s
ci / check (pull_request) Successful in 1m13s
ci / images (pull_request) Successful in 5s
2026-10-28 17:13:31 -05:00
iamdoubz 0a18d5354d asterisk: 24.0.0-rc3, TLS trunks qualified again
rc3 checks a qualify's certificate against the host name, so TLS trunks get
qualify_frequency = 60 and Test qualifies them. The upgrade check and
pnpm test:asterisk pass on rc3. Spec 01's copy of the Dockerfile is in sync
again, and the version test now compares all of it.
2026-10-28 17:11:18 -05:00
iamdoubz a0121d4975 test: Asterisk upgrade check
verified.ts records what the code relies on Asterisk doing and the version it
was measured on; pnpm test fails while the Dockerfile pins another one.
pnpm test:asterisk-upgrade boots the image next to a TLS provider and lists
every behaviour that moved and what depends on it.
2026-10-28 17:01:26 -05:00
iamdoubz 60348cf0c5 security: a key for the network test probe (08 §7 #9)
ci / check (push) Successful in 1m13s
ci / images (push) Successful in 4m2s
ci / check (pull_request) Successful in 1m42s
ci / images (pull_request) Successful in 28s
Settings → Network makes a key, shown once and stored encrypted; the api sends
it to the probe as a Bearer token, and a new probe address drops it. A probe
with PROBE_KEY only answers that key. Without one it stays open, and
Diagnostics strongly recommends setting it.
2026-10-28 16:28:23 -05:00
iamdoubz 958405651b docs: security review 2 (08 §7) 2026-10-28 16:01:57 -05:00
iamdoubz db244b116a security: recordings, webhooks, Contrack tokens, probe (08 §7 #3 #4 #6 #7)
A phone that only rang no longer gets a coworker's recording, transcript or
summary. The voicemail webhook leaves the transcript out. The saved Contrack
token only goes to its own address. The probe trusts X-Forwarded-For from a
local proxy only.
2026-10-28 16:01:56 -05:00
iamdoubz 1db5aa22dc security: sign-in by email and connecting accounts (08 §7 #1 #2 #5 #8)
Matching by email skips admins and anyone with a connected account, in both
directions. A connected directory account is only swapped by an admin, and
an OpenID connect finishes only in the browser whose session asked.
2026-10-28 16:01:56 -05:00
iamdoubz a75113b6ac web: report spam from my calls, reports on Blocked numbers
ci / check (push) Successful in 1m24s
ci / images (push) Successful in 25s
ci / check (pull_request) Successful in 1m15s
ci / images (pull_request) Successful in 7s
Users report an outside call that rang them: the spam check's line when it
said medium or high, else a reason they type. Admins block or dismiss each
report on Blocked numbers, and the Dashboard counts them. Migration 0015 adds
spam_reports.
2026-10-28 15:07:05 -05:00
iamdoubz 2bc7d5cde5 web: my reports, the calls made to me
Users get a Reports tab: the admin summary limited to calls that rang or were
answered by them. Answered and talk time are the calls they picked up.
2026-10-28 13:39:33 -05:00
iamdoubz 19ca4ffc91 docs: v1 checklist ticks, DID in the Request-URI
ci / check (push) Successful in 1m12s
ci / images (push) Successful in 30s
The outside install, restores, the week on the owner's server, Portainer
updates and both registries are done. The mobile app is after v1.
2026-10-28 13:28:42 -05:00
iamdoubz 1bc0fe8811 docs: CrowdSec rule for slow SIP scanners
ci / check (push) Successful in 1m21s
ci / images (push) Successful in 22s
The collection's asterisk_bf and asterisk_user_enum hold 5 failures per
address and drain one every 10 s, so a scanner trying every 20 s (two
failures per try: InvalidAccountID, ChallengeResponseFailed) drains as
fast as it fills and is never banned. Seen on a live install: hours of
INVITEs from one address, no decision.

network.md: a local scenario, selfphoneng/asterisk-slow-bf (10 failures,
one drained every 5 min), a table of how it differs from the stock rule,
a whitelist for remote offices and phones, how to read cscli metrics,
and cscli explain with a log line (pointing --file at the acquisition
YAML parses the YAML itself and fails on every line).
2026-10-28 10:56:40 -05:00
iamdoubz 4369dd5ae1 release: version 0.0.11
ci / check (push) Successful in 1m7s
ci / images (push) Successful in 22s
ci / check (pull_request) Successful in 1m10s
ci / images (pull_request) Successful in 9s
2026-10-28 10:04:42 -05:00
iamdoubz 79bb1fa78b release: version 0.0.10
ci / check (push) Successful in 1m7s
ci / images (push) Successful in 5s
release / images (release) Successful in 2m53s
2026-10-27 18:37:53 -05:00
iamdoubz 6b0c5aa827 web: spam check in call history
ci / check (push) Failing after 1m9s
ci / images (push) Successful in 44s
SD5 (14 §9.5): each incoming call keeps what the spam check knew when it
ended (saved answers and the FTC list only, no new lookup), so the badge
stays in call history after the saved answer expires. Hidden when the
viewer's contacts name the number. New admin guide page, spam-check.md.

Follow-up: the owner's checks with real keys and a real call (14 §9).
2026-10-27 18:16:24 -05:00
iamdoubz e05065599e web: show the spam check while a call rings
SD4 (14 §5): GET /api/me/caller-check for callers ringing my extensions
now, a badge and line in the softphone's ringing dialog and call card,
and the desk phone label in the caller-name lookup when the answer comes
within 1.2 s. Contacts are never checked; low risk shows nothing.

Follow-up: try it on a real call, in the browser and on a desk phone.
2026-10-27 16:51:29 -05:00
iamdoubz 49d529e185 api: download the FTC complaint list
SD3 (14 §2.2): spamFtcSync reads the FTC's Do Not Call complaints a day
at a time, 15 pages a minute, saving its place; failures are kept for
Settings → Spam check and retried the next minute. Download now starts
one. Old complaints are dropped after windowDays.

Follow-up: one real download with the owner's api.data.gov key.
2026-10-27 16:40:14 -05:00
iamdoubz 70f14288ca api: pause spam check when IPQualityScore credits run out
SD2 (14 §3): out of credits pauses IPQualityScore (one warning, one
event), the FTC list answers meanwhile, the dashboard shows a dismissable
spam_quota warning, and spamQuotaCheck resumes it every 6 hours once
credits are back. A newly saved key resumes it at once.

Follow-up: confirm with the owner's real key that the Credit Usage API
costs no credit (TODO in 14 §2.1).
2026-10-27 16:17:42 -05:00
iamdoubz 6f87d6f663 api: spam check settings, lookups and cache
SD1 (14 §9): the spam/lookups tables and calls columns, the
IPQualityScore and FTC lookups with the cache, the spam settings with
write-only keys, the provider Test routes and Settings → Spam check.
2026-10-27 15:32:31 -05:00
iamdoubz a3dfcb6b14 docs: spam check spec
SD1 (spec 14, decision 0006): IPQualityScore first, then the FTC Do Not
Call complaint list, for outside US callers who aren't in anyone's
contacts. Information only, off by default; credits running out pause
IPQualityScore until its credit usage API says they're back.
2026-10-27 13:51:08 -05:00
iamdoubz dc5965617e release: version 0.0.9
ci / check (push) Successful in 1m4s
ci / images (push) Successful in 5s
release / images (release) Successful in 1m33s
2026-10-27 12:49:05 -05:00
iamdoubz 33d5f1a5f5 web: answer incoming calls from the browser notification
ci / check (push) Successful in 1m34s
ci / images (push) Successful in 27s
ci / check (pull_request) Successful in 1m18s
ci / images (pull_request) Successful in 5s
Owner follow-up to the softphone (spec 05 §5).

- the incoming call notification comes from a service worker
  (public/sw.js) so it carries Answer and Decline buttons (Chromium);
  Answer and a plain click focus the softphone tab, Decline doesn't
- shown whenever the tab isn't focused, not only when it's hidden; kept
  up while it rings and closed once it stops
- the softphone panel says how to allow notifications when they're blocked
2026-10-27 12:35:43 -05:00
iamdoubz 431ada51aa docs: confirm the mobile app id
ci / check (push) Successful in 1m30s
ci / images (push) Successful in 22s
MB3
2026-10-26 20:54:54 -05:00
iamdoubz 7da0611a98 docs: mobile app MB3 as built
MB3: the Expo skeleton and pairing are in the mobile repo (6e4bc2b);
the device test with a real Android phone and iPhone is still open.
2026-10-26 17:45:28 -05:00
iamdoubz 0cdac0bcdc api: pair the mobile app with device tokens
MB1 (spec 13). Decision 0005: our own Android/iPhone app, Expo in the
mobile repo, user portal only, no push in v1.

- mobile_devices (migration 0013): one-time pairing codes from My settings
  (QR or server + code, 10 minutes), traded by the app for an spm_ device
  token that acts as its user and never as an admin
- POST /api/me/mobile-devices/pair (web session only), POST /api/mobile/pair,
  GET/DELETE /api/me/mobile-devices
- signing out everywhere (password change or reset, disabling) removes
  paired phones too
- My settings: Mobile app section with the QR code and paired phones
- mobile/ (its own repo) ignored here
2026-10-26 17:12:53 -05:00
iamdoubz adb6d52ddf release: version 0.0.8
ci / check (push) Successful in 1m3s
ci / images (push) Successful in 19s
release / images (release) Successful in 59s
2026-10-26 09:55:54 -05:00
iamdoubz 97e67e347d auth: security audit fixes, rate limits on every route
ci / check (push) Successful in 1m3s
ci / images (push) Successful in 19s
ci / check (pull_request) Successful in 1m5s
ci / images (pull_request) Successful in 5s
Two separate reviews of the SSO branch and all 207 API routes (spec 12 §8.6). Every route was
guarded as meant. Fixed, each with a test:
- login: a directory 503 counts as a failed sign-in; a locked directory account is a refused
  password, not "unavailable"
- open redirect after OpenID sign-in (a tab in next)
- directory email matching only for non-admins without their own password; others connect on
  My settings (POST /api/me/identities/ldap); only admins disconnect a directory account
- OpenID session check reads the refreshed ID token's claims too
- connect is POST /api/auth/oidc/connect (CSRF header)
- stored bind password, client secret and SMTP password tied to their server
- 409 role_managed when a provider sets the role; empty Contrack PATCH is a 400
- @fastify/rate-limit on every route (RATE_LIMIT_PER_MINUTE), 10/min on Test buttons; a
  password asked again shares the login limiter
Live tests pass against lldap 0.6.3, Samba 4 AD and Keycloak 26.8.1.
2026-10-26 09:46:23 -05:00
iamdoubz 19610777a4 auth: directory leavers check, dashboard warning, Samba AD check (S5)
ci / check (push) Successful in 1m22s
ci / images (push) Successful in 22s
Spec 12 S5: job directoryCheck (hourly) looks up every active user with a directory account;
one deleted, disabled there or no longer matched by the user filter is disabled here, sessions
ended. Nothing changes when the directory is unreachable or a run would disable most people;
the last admin with a password is kept; nobody is re-enabled. Dashboard warning
directory_check (system.directoryWarning). The by-id search is a filter object: ldapts string
filters send bytes >= 0x80 as UTF-8, so AD's objectGUID never matched. Live test against a
Samba 4 AD DC: nested-group admin, the disabled bit, objectGUID, a deleted user disabled.
2026-10-26 07:47:12 -05:00
iamdoubz b06335586b auth: sign in with LDAP/LDAPS, directory settings and Test (S4)
Spec 12 S4: the login form checks the SelfPhoneNG password first, then the directory (ldapts,
service-account search, the user's own bind on a second connection). Presets for lldap, AD,
FreeIPA and OpenLDAP; Test with an optional test login. A directory account connects by email,
never makes a user; adminFilter sets the role (applyRole shared with OpenID). One Disconnect
route per kind (oidc, ldap). Live test against lldap 0.6.3 over LDAPS.
2026-10-25 18:39:52 -05:00
iamdoubz 73963ee3a3 auth: OpenID session check, live Keycloak test, sign-in guide (S3)
Spec 12 S3: job oidcCheck refreshes every OpenID session at the provider every 15 minutes and
applies groups and role again; invalid_grant ends the session, an outage doesn't. A live test
makes a throwaway Keycloak realm and signs in through its login form (passed against 26.8.1).
NODE_EXTRA_CA_CERTS for a private CA. Admin guide: docs/admin-guide/sign-in.md.
2026-10-25 15:14:59 -05:00
iamdoubz 06b3a73edc auth: sign in with OpenID Connect, connect and disconnect, sign-in modes (S2)
Spec 12 S2: openid-client 6.8 (decision 0004). Discovery, PKCE + state + nonce, ID token
signatures checked, userinfo always read. Users connect their OpenID account from My account
and sign in either way; no accounts are made; a user without a password is matched once by
verified email. auth.mode mixed and oidc (admins keep a password way in), role from adminGroups,
allowedGroups, signing out at the provider. Settings -> Sign-in with Test. Tested against a fake
provider with RS256 and ES256 keys.
2026-10-25 14:13:27 -05:00
iamdoubz 3a9423af1f auth: sign-in ways, sign-in mode, and the admin who keeps a password (S1)
Spec 12 S1: migration 0012 (password_hash nullable, user_identities, sessions.method and the
OpenID token columns), auth.mode (only "password" until S2), GET /api/auth/methods, login body
{login, password} (email still accepted), signIn on users and /api/auth/me, a Signs in with
column on Users. The last active admin with a password can't be deleted, disabled or demoted.
2026-10-25 13:30:58 -05:00
iamdoubz ba293f8703 spec: single sign-on (12), OpenID Connect and LDAP
Owner decisions 2026-10-25: a user connects their OpenID account and signs in with either it or
their password; the admin picks passwords only, OpenID only (admins keep a password way in), or
mixed. No automatic accounts. OIDC sessions re-checked every 15 min with the refresh token;
signing out also signs out at the provider; password sign-ins keep two-step. LDAP is one more way
to check a password. Decision 0004: openid-client and ldapts. Tasks S1-S5.
2026-10-25 13:09:45 -05:00
iamdoubz 95561f651f release: version 0.0.7
ci / check (push) Successful in 1m4s
ci / images (push) Successful in 5s
release / images (release) Successful in 31s
2026-10-23 16:16:37 -05:00
iamdoubz 604e40adf1 contacts: owners' contacts on their desk phones, copy builtin contacts to Contrack (C6)
ci / check (push) Successful in 1m2s
ci / images (push) Successful in 20s
ci / check (pull_request) Successful in 1m4s
ci / images (pull_request) Successful in 5s
Spec 11 §6.2: a phone's directory is the extensions, its owner's own contacts (accepted shares
included), then the company's, in both modes. In Contrack mode, contacts from before Contrack
are offered for copying (contrack.toCopy, POST …/copy-to-contrack: one Contrack bulk import,
skipping what's already there; the builtin rows stay). Tested against a fake and a real
Contrack 2.0.2.
2026-10-23 16:07:19 -05:00
iamdoubz e6a1373f36 contacts: calls on the Contrack timeline, summaries opt-in (C5)
ci / check (push) Successful in 1m7s
ci / images (push) Successful in 20s
Spec 11 §4.4: the contrackLog job puts each connected person's calls with their Contrack
contacts on that contact's timeline once (contrack_call_log, migration 0011), from when they
connected. With summaries on (My settings → Contacts, needs call summaries), the escaped summary
and action items go in, and the first action item becomes a follow-up due the next day. Waits up
to 30 min for a summary; Contrack down is retried, refusals noted once. Tested against a fake and
a real Contrack 2.0.2.
2026-10-23 15:59:53 -05:00
iamdoubz b05361e840 contacts: caller names on desk phones while they ring (C4)
ci / check (push) Successful in 1m2s
ci / images (push) Successful in 22s
Spec 11 §6.1: sp-sub-dial-user asks /api/internal/caller-name (func_curl, 2 s at most; this
machine only, with an APP_SECRET-derived key, not logged) for the ringing user's name for the
caller, and sets it on each ringing leg (pre-dial handler, CONNECTEDLINE), never on the call, so
forwards and other members don't see someone else's contacts. Ring group/queue/number prefixes
kept. Contrack lookups never wait (cache, contactsWarm every 5 min). Proven on real Asterisk
with baresip: apps/api/test/asterisk/cnam-test.ts.
2026-10-23 15:45:36 -05:00
iamdoubz 692dd59552 contacts: Contrack as the robust contacts (C3), picked in Settings or the setup wizard
ci / check (push) Successful in 1m0s
ci / images (push) Successful in 32s
Spec 11 §4: contacts.provider and contacts.contrack (local address only, company token
write-only), each person's own token (users.contrack_token_enc, migration 0010) in My settings,
books read from Contrack with a 5-minute cache, Add to contacts and accepted shares created in
Contrack, the company account on desk phones. Setup wizard step 8 Contacts. deploy/contrack
(2.0.2, pinned), Caddy/nginx blocks, decision 0003, admin guide Contacts. Tested against a fake
and a real Contrack 2.0.2 (contrack-live.test.ts).
2026-10-23 15:23:49 -05:00
iamdoubz a754974a90 contacts: my book, the company book, vCard/CSV import, sharing with Accept/Deny
C1 (spec 11): /api/me/contacts and /api/contacts (admin), our own vCard 2.1/3.0/4.0 and CSV
readers with a preview, contact_shares (migration 0009) for offers, and contact names in call
history, voicemail, Home, the softphone and the call report email. Admin → Contacts.
2026-10-23 14:53:21 -05:00
iamdoubz 32f4590369 spec: contacts (11), Simple or Contrack, picked at setup
Owner decisions 2026-10-23: vCard import for admins and users; desk-phone caller name while
ringing is its own task (C4); the company is one Contrack account shown to everyone, each user
has their own; sharing offers a copy the colleague accepts or denies; imports skip duplicates.
2026-10-23 14:53:21 -05:00
iamdoubz 4c006f2388 me: call report email (hourly, daily, weekly), with call summaries
ci / check (push) Successful in 1m14s
ci / images (push) Successful in 40s
ci / check (pull_request) Successful in 59s
ci / images (pull_request) Successful in 4s
My settings → Call report: each user's calls of the last hour, day (at a time) or week (on a
day at a time), company time zone: numbers, status, talk time, recorded, and optionally each
recorded call's summary (10 §3). No calls, no email. Job callReports every minute, 5 min after
the period ends; periods computed in PostgreSQL (DST-safe). Migration 0008_call_reports.
2026-10-23 13:55:47 -05:00
iamdoubz aaa294ae6a release: version 0.0.6
ci / check (push) Successful in 1m17s
ci / images (push) Successful in 11s
release / images (release) Successful in 54s
2026-10-21 20:24:46 -05:00
iamdoubz 4d331fb70e ai: call transcripts (each side recorded, merged by time) and summaries; search on Recordings
ci / check (push) Successful in 1m22s
ci / images (push) Successful in 27s
2026-10-21 19:10:02 -05:00
iamdoubz d9f9c6e444 release: version 0.0.5
ci / check (push) Successful in 1m27s
ci / images (push) Successful in 26s
2026-10-21 18:46:04 -05:00
iamdoubz a26a5ca740 ai: Settings → AI (local endpoints only, with tests), the AI stack, voicemail transcripts
ci / check (push) Successful in 1m0s
ci / images (push) Successful in 26s
2026-10-21 18:37:13 -05:00
iamdoubz 274242ce32 roadmap: a backlog, the HT802 V2 setup first
ci / check (push) Successful in 1m0s
ci / images (push) Successful in 26s
2026-10-21 17:57:32 -05:00
iamdoubz 0deff2a637 dial plan: made from routes and number lengths; Yealink dial-now, the ATA plan on the Credentials tab 2026-10-21 17:14:13 -05:00