Skip to main content
Version: 0.9

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

ComponentImagePurposeStateful?
APItokentimer-enterprise-apiREST API (Node.js, Express), authentication, license verification, audit loggingNo (state in DB)
Dashboardtokentimer-enterprise-dashboardReact UI served by nginx, talks to the APINo
Worker - discoverytokentimer-enterprise-workerScans tokens nearing expiry and queues alert jobsNo
Worker - deliverytokentimer-enterprise-workerConsumes the alert queue and sends email / Slack / Teams / Discord / WhatsApp / webhooksNo
Worker - weekly digesttokentimer-enterprise-workerSends each user a weekly summary of expiring itemsNo
Worker - endpoint checktokentimer-enterprise-workerPolls configured HTTPS endpoints to detect SSL and health regressionsNo
Worker - auto-synctokentimer-enterprise-workerPulls token metadata from configured providers (AWS, Azure, GCP, Vault, GitHub, GitLab)No
PostgreSQLpostgres:17-alpine (Compose) or CloudNativePG (Kubernetes)Single source of truth for all persistent stateYes
Migrations jobtokentimer-enterprise-api (run once)Applies schema changes on every release before API startsNo

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 to SESSION_SECRET).

What is stored where

DataLocationNotes
Tokens, certificates, secrets metadataPostgreSQLOnly metadata is stored: name, owner, type, expiration, source. Never the secret value or private key itself.
Workspaces, users, rolesPostgreSQLLocal password hash (bcrypt), 2FA seed, auth_method (local or sso; protocol per row in external_identities)
Audit logPostgreSQLLogin attempts, SSO callbacks, license denials, admin changes, token CRUD
Alert queuePostgreSQLEach pending notification with retry counters and delivery window
System settingsPostgreSQLSMTP, Twilio, license JWT. Takes precedence over env vars when set in the System Settings UI.
Session cookiesBrowserSigned by SESSION_SECRET. Marked Secure in production.
License fileMounted 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)

  1. Browser hits https://tokentimer.your-domain.com/auth/oidc/<slug> on the API (slug from login button or IdP app registration).
  2. API verifies the effective license entitles feature:oidc. If not, redirects to /login?error=license_oidc_required and writes a LICENSE_DENIED audit row.
  3. API redirects the browser to the IdP authorization URL.
  4. IdP authenticates the user and redirects to /auth/oidc/<slug>/callback with an authorization code.
  5. 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 = true when group mappings or config.bootstrap_admin_groups on 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=true AND (for OIDC) the IdP asserted email_verified=true. SAML callers count as verified by virtue of the cert-signed assertion.
  6. API sets the session cookie and redirects to the dashboard. The whole interaction is audit-logged.

Auto-sync (e.g. AWS Secrets Manager)

  1. The auto-sync CronJob (every 1 hour by default) wakes up.
  2. It reads all enabled auto-sync configs from PostgreSQL, scoped per workspace.
  3. For each AWS config, it assumes the configured IAM role / uses static credentials and lists secrets.
  4. Discovered tokens are upserted into PostgreSQL with source = 'auto-sync:aws' and a refresh timestamp.
  5. The discovery worker (every 5 minutes) sees the refreshed expirations and enqueues alerts as thresholds are crossed.
note

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, verifyEffectiveLicense runs:
    1. Read the latest license JWT from system_settings.license_jwt (admin imported), otherwise the file at TT_LICENSE_PATH.
    2. Verify the JWT signature.
    3. Compare exp to now, factoring in TT_LICENSE_GRACE_DAYS (default 30).
  • If still valid: serve the request normally; add an X-License-Warning header inside the grace window.
  • If past grace: SSO routes redirect, auto-sync POST/PUT/run return 403 LICENSE_INVALID, and a LICENSE_DENIED audit row is written. Read and DELETE on existing configs still works so admins can clean up.

Network requirements

FromToProtocolPurpose
BrowsersDashboardHTTPS (443)UI
BrowsersAPIHTTPS (443)REST and session cookies
BrowsersIdPHTTPSSSO redirect (browser-mediated)
APIPostgreSQLTCP 5432Primary state store
APIIdP discovery / userinfoHTTPSOIDC .well-known and userinfo, SAML metadata
APISMTP hostTCP 465 / 587Email
APITwilioHTTPSWhatsApp (optional)
WorkersPostgreSQLTCP 5432Job queue and metadata
WorkersProvider APIs (AWS, Azure, GCP, Vault, GitHub, GitLab)HTTPSAuto-sync
WorkersSMTP / Twilio / webhook hostsHTTPS or TCP 465/587Notifications
Worker (auto-sync)APIHTTP/S to API serviceRe-using API helpers via WORKER_API_KEY
Nodes pulling imagesharbor.tokentimer.ch:443HTTPSImage 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-enterprise project 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 .env example 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 requires email_verified=true on 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.