--- tags: - service - josiedot - irc-now domain: my.irc.now status: active stack: - Rust - Axum - Keycloak OIDC - PostgreSQL auth: Keycloak --- # irc.now Account Portal (my.irc.now) Account management portal for irc.now users. Handles authentication, bouncer provisioning, and billing. ## URLs - https://my.irc.now ## Stack - Rust (Axum 0.8, openid 0.23, tower-sessions 0.15, askama 0.15) - PostgreSQL via CNPG (accounts-db) - kube.rs 3.0 for SojuBouncer CR management - Stripe (async-stripe 0.41) for billing webhooks ## Auth Keycloak OIDC via `irc-now` realm, client `account-portal`. - Login: /auth/login -> Keycloak -> /auth/callback - Logout: /auth/logout -> Keycloak end-session - Session: tower-sessions MemoryStore ## Routes - /auth/login, /auth/callback, /auth/logout - /dashboard - /bouncers, /bouncers/create, /bouncers/:name/delete - /billing, /billing/webhook - /migrate (GET form, POST import) - /profile, /profile/update ## Deployment - OCP namespace: irc-josie-cloud - ServiceAccount: web-api (RBAC for sojubouncers CRUD, secrets get) - Build: `oc start-build web-api --from-archive` (rust:1.88 -> ubi9-minimal) - TLS: cert-manager Certificate CR -> externalCertificate on Route - Certificate: `my-irc-now-cert` / secret `my-irc-now-tls` - Env from secrets: accounts-db-app, oidc-account-portal, stripe-keys ## Source - Repo: ~/development/irc-now - Crate: `crates/web-api/` ## Profile - Display name, email with Keycloak sync - Content settings: auto-expire toggle (pro only, default on, 90-day expiry) - Keycloak attribute sync: plan + content_expires pushed to user attributes on login, profile save, and Stripe webhook events - Synced attributes available as ID token claims for txt/pics ## IRCCloud Migration - `/migrate` imports IRC connections from IRCCloud into a user's soju bouncer - Authenticates with IRCCloud API (formtoken + login), streams initial state - Extracts servers (makeserver) and channels (makebuffer) from the event stream - Auto-creates bouncer if user doesn't have one, waits for DB provisioning - Inserts networks + channels into soju tenant DB, adds default irc.now upstream - Credentials used once for fetch, not stored - Source: `src/irccloud.rs` (API client), `src/routes/migrate.rs` (handlers) ## Business Metrics Background tasks record Prometheus gauges every 60s (user metrics) and 300s (soju metrics): - `irc_now_users_total`, `irc_now_users_by_plan{plan}`, `irc_now_active_users_24h` - `irc_now_logins_total` (counter, incremented on each OIDC callback) - `irc_now_bouncer_networks_total`, `irc_now_bouncer_channels_total`, `irc_now_bouncer_messages_total`, `irc_now_bouncer_active_users` - Soju metrics connect to each bouncer's tenant DB via K8s secrets - `last_login_at` column on users table tracks DAU (migration: `migrations/001_add_last_login_at.sql`) ## Bouncer Watcher kube.rs watcher on SojuBouncer CRs, spawned at startup alongside business metrics tasks. - Watches for bouncers transitioning to `Ready=True` - On ready (and no `irc.now/upstream-configured` annotation): 1. Connects to the bouncer's soju tenant DB (via `{name}-db` secret) 2. Waits for soju User table to populate (retries up to 10x) 3. Ensures ergo account exists with correct password (`NS SAREGISTER` / `NS SAPASSWD`) 4. UPSERTs `irc.now` network with SASL PLAIN credentials 5. Annotates CR with `irc.now/upstream-configured: "true"` to prevent re-processing - Handles existing bouncers on startup (initial list triggers Applied events for all CRs) - Replaces previous fire-and-forget spawn (raced soju-operator) and login-time backfill (had password mismatch bug) - Source: `src/bouncer_watcher.rs`, `src/ergo_admin.rs` ## Related - [[irc-now-landing]] - [[irc-now-keycloak]]