Architecture overview
A concise overview of what runs where, what data lives where, and how requests flow through the stack. For registry access see Registry access.
Components
| Component | Image | Purpose | Stateful? |
|---|---|---|---|
| API | tokentimer-enterprise-api | REST API (Node.js, Express), authentication, license verification, audit logging | No (state in DB) |
| Dashboard | tokentimer-enterprise-dashboard | React UI served by nginx, talks to the API | No |
| Worker - discovery | tokentimer-enterprise-worker | Scans tokens nearing expiry and queues alert jobs | No |
| Worker - delivery | tokentimer-enterprise-worker | Consumes the alert queue and sends email / Slack / Teams / Discord / WhatsApp / webhooks | No |
| Worker - weekly digest | tokentimer-enterprise-worker | Sends each user a weekly summary of expiring items | No |
| Worker - endpoint check | tokentimer-enterprise-worker | Polls configured HTTPS endpoints to detect SSL and health regressions | No |
| Worker - auto-sync | tokentimer-enterprise-worker | Pulls token metadata from configured providers (AWS, Azure, GCP, Vault, GitHub, GitLab) | No |
| PostgreSQL | postgres:17-alpine (Compose) or CloudNativePG (Kubernetes) | Single source of truth for all persistent state | Yes |
| Migrations job | tokentimer-enterprise-api (run once) | Applies schema changes on every release before API starts | No |
All five workers run the same image and differ only by entrypoint command and CronJob schedule (Kubernetes) or container command (Compose). They are stateless and horizontally scalable.
Deployment topology
Internet / IdP / Notification channels
|
v
+---------------------------+
| Ingress / Reverse proxy |
| (HTTPS termination) |
+-------------+-------------+
|
+-----------------------+----------------------+
| | |
v v v
+---------------+ +---------------+ +-----------------+
| Dashboard | | API | | Workers (5) |
| (nginx) |----->| (Node.js) |<-----| discovery |
| | | session, | | delivery |
+---------------+ | RBAC, audit, | | weekly-digest |
| license check | | endpoint-check |
+-------+-------+ | auto-sync |
| +--------+--------+
v |
+---------------+ |
| PostgreSQL |<-------------+
+---------------+
- The dashboard talks only to the API (no DB access of its own).
- The API talks to PostgreSQL for all reads and writes, to the configured IdP (OIDC discovery and SAML metadata) when SSO is enabled, and to SMTP / Twilio / webhook providers for sending verification emails and inbound test messages.
- Workers read jobs from the alert queue table in PostgreSQL and talk
outward to provider APIs (AWS, Azure, etc.), SMTP, Twilio, and webhook
destinations. The auto-sync worker also calls the API back over HTTP using a
shared secret (
WORKER_API_KEY, falls back toSESSION_SECRET).
What is stored where
| Data | Location | Notes |
|---|---|---|
| Tokens, certificates, secrets metadata | PostgreSQL | Only metadata is stored: name, owner, type, expiration, source. Never the secret value or private key itself. |
| Workspaces, users, roles | PostgreSQL | Local password hash (bcrypt), 2FA seed, auth_method (local or sso; protocol per row in external_identities) |
| Audit log | PostgreSQL | Login attempts, SSO callbacks, license denials, admin changes, token CRUD |
| Alert queue | PostgreSQL | Each pending notification with retry counters and delivery window |
| System settings | PostgreSQL | SMTP, Twilio, license JWT. Takes precedence over env vars when set in the System Settings UI. |
| Session cookies | Browser | Signed by SESSION_SECRET. Marked Secure in production. |
| License file | Mounted volume (TT_LICENSE_PATH) | Read on every license check; cached briefly. DB-stored JWT (System Settings) takes precedence when present. |
The API does not persist anything to disk other than logs to stdout. PostgreSQL is the only stateful component.
Data flow examples
Login (SSO with OIDC)
- Browser hits
https://tokentimer.your-domain.com/auth/oidc/<slug>on the API (slug from login button or IdP app registration). - API verifies the effective license entitles
feature:oidc. If not, redirects to/login?error=license_oidc_requiredand writes aLICENSE_DENIEDaudit row. - API redirects the browser to the IdP authorization URL.
- IdP authenticates the user and redirects to
/auth/oidc/<slug>/callbackwith an authorization code. - API exchanges the code for an ID token plus userinfo, extracts email and
groups, then:
- Looks up the user by canonicalized email. If new, inserts
(
auth_method='sso') and joins the shared Default workspace (or accepts SSO group mappings when configured). - Sets
is_admin = truewhen group mappings orconfig.bootstrap_admin_groupson the provider row match. - Rejects the login when subject lookup misses and the email match would
otherwise silently link the IdP identity to an existing local-password or
SSO account, unless
SSO_ALLOW_AUTO_LINK_LOCAL_ACCOUNTS=trueAND (for OIDC) the IdP assertedemail_verified=true. SAML callers count as verified by virtue of the cert-signed assertion.
- Looks up the user by canonicalized email. If new, inserts
(
- API sets the session cookie and redirects to the dashboard. The whole interaction is audit-logged.
Auto-sync (e.g. AWS Secrets Manager)
- The auto-sync CronJob (every 1 hour by default) wakes up.
- It reads all enabled auto-sync configs from PostgreSQL, scoped per workspace.
- For each AWS config, it assumes the configured IAM role / uses static credentials and lists secrets.
- Discovered tokens are upserted into PostgreSQL with
source = 'auto-sync:aws'and a refresh timestamp. - The discovery worker (every 5 minutes) sees the refreshed expirations and enqueues alerts as thresholds are crossed.
The worker does NOT re-verify the enterprise license on each run. License gating happens at config creation / update (API endpoint requires the entitlement). To stop a provider after a license downgrade, delete its auto-sync config from the dashboard.
License grace and expiry
- On every API request that touches an enterprise-gated route,
verifyEffectiveLicenseruns:- Read the latest license JWT from
system_settings.license_jwt(admin imported), otherwise the file atTT_LICENSE_PATH. - Verify the JWT signature.
- Compare
expto now, factoring inTT_LICENSE_GRACE_DAYS(default 30).
- Read the latest license JWT from
- If still valid: serve the request normally; add an
X-License-Warningheader inside the grace window. - If past grace: SSO routes redirect, auto-sync POST/PUT/run return
403 LICENSE_INVALID, and aLICENSE_DENIEDaudit row is written. Read and DELETE on existing configs still works so admins can clean up.
Network requirements
| From | To | Protocol | Purpose |
|---|---|---|---|
| Browsers | Dashboard | HTTPS (443) | UI |
| Browsers | API | HTTPS (443) | REST and session cookies |
| Browsers | IdP | HTTPS | SSO redirect (browser-mediated) |
| API | PostgreSQL | TCP 5432 | Primary state store |
| API | IdP discovery / userinfo | HTTPS | OIDC .well-known and userinfo, SAML metadata |
| API | SMTP host | TCP 465 / 587 | |
| API | Twilio | HTTPS | WhatsApp (optional) |
| Workers | PostgreSQL | TCP 5432 | Job queue and metadata |
| Workers | Provider APIs (AWS, Azure, GCP, Vault, GitHub, GitLab) | HTTPS | Auto-sync |
| Workers | SMTP / Twilio / webhook hosts | HTTPS or TCP 465/587 | Notifications |
| Worker (auto-sync) | API | HTTP/S to API service | Re-using API helpers via WORKER_API_KEY |
| Nodes pulling images | harbor.tokentimer.ch:443 | HTTPS | Image and Helm chart pulls |
No inbound TokenTimer-initiated traffic to your network is required. No telemetry is sent to TokenTimer; license verification is fully offline.
Security model summary
- No imported secret values stored: TokenTimer tracks expiration metadata only for assets you import. A leaked database does not expose your tracked tokens, certs, or API key values. Integration scan credentials are discarded after one-off imports; if you enable auto-sync, they are encrypted at rest for scheduled re-scans.
- License is signed offline: The JWT is RS256-signed by TokenTimer. Verification happens locally on each API request; no outbound call to TokenTimer is required.
- Pull-only registry access: Your robot account can only pull from the
tokentimer-enterpriseproject in Harbor and cannot list or mutate any other project. - Default hardened containers: Non-root user, read-only root filesystem,
dropped capabilities,
seccompProfile: RuntimeDefault, no service-account token mounted by default. - CSRF, session hardening, rate-limiting, slowdowns: enabled by default in
production; configurable via env (see the
.envexample in the delivery). - SSO with silent-takeover guard: SSO refuses to fall back to email
matching against an existing local-password or SSO account unless explicitly
enabled (
SSO_ALLOW_AUTO_LINK_LOCAL_ACCOUNTS=true). OIDC also requiresemail_verified=trueon the email-fallback path.
What you DO NOT need to operate TokenTimer
- TokenTimer source code
- A build toolchain (Node.js, pnpm, Docker buildx)
- Outbound access to GitHub
- Communication with TokenTimer at runtime
Everything you need ships in the delivery tarball and in pre-built images
pulled from harbor.tokentimer.ch.