# CLAUDE.md — OSA Management Suite ## Project Overview Internal management tool with four domains: Projects, Users, Licenses, and Certificates. Includes a feedback system, sponsorship tracking, email notifications, and admin panel. Monorepo with a Flask backend and React frontend, deployed to Kubernetes via Helm. ## Repository Structure ``` backend/ Python Flask API app/ auth/ Session-based auth + Keycloak OIDC login (login_required, project_access_required decorators) projects/ Project CRUD, member management (KC-based), sponsorship, verification, group management users/ User CRUD, ProjectUser join model with billing types licenses/ License CRUD with file upload/download certs/ Cert CRUD with encrypted key + passphrase storage, format conversion dashboard/ Stats (including sponsorship metrics), expiring items, audit log (limited to 10 most recent) downtime/ Downtime Tracker CRUD (application outage logging with enclave, scope, planned/unplanned) feedback/ Feedback CRUD with screenshot storage and status workflow settings/ App-level settings (expiry thresholds, notification config, sponsorship, work hours) with key-value store keycloak.py Keycloak Admin API client (users, groups, child groups, membership, sponsor attribute) keycloak_permissions.py Derives app permissions from KC group memberships (cached in session) email.py SMTP email client (send_email helper, configurable via env vars) crypto.py Fernet encrypt/decrypt helpers audit.py AuditLog model + log_audit() helper config.py All config via env vars (includes SMTP_* settings) seed.py Seeds projects, licenses, and certs into local DB (users are Keycloak-only) run.py Entry point (db.create_all() on startup, manual ALTER TABLE for schema changes) frontend/ React + TypeScript src/ pages/ One file per route (login, dashboard, projects-list, project-detail, users-list, user-detail, licenses-list, certs-list, downtime-list, admin) components/ ui/ shadcn/ui primitives (do not edit manually) shared/ Reusable components (data-table, stat-card, status-badge, activity-feed, etc.) projects/ Project-specific components (group-management for KC groups, project-form) users/ User-specific components (columns, form) licenses/ License-specific components certs/ Cert-specific components (cert-form with passphrase reveal) downtime/ Downtime components (downtime-columns with work-hours classification, downtime-form) dashboard/ Dashboard widgets (stats-overview with sponsorship metrics, expiring-items, recent-activity) feedback/ Feedback modal (screenshot attach with auto-scaling) layout/ App shell, sidebar (with admin nav), topbar, protected-route hooks/ React Query hooks (use-projects, use-users, use-project-users, use-project-members, use-licenses, use-certs, use-downtime, use-feedback, use-settings, use-keycloak-groups, use-keycloak-sync) contexts/ Auth context provider (is_admin flag, permissions helpers, OIDC logout) lib/ API client (axios), utils, constants chart/osa-suite/ Helm chart for Kubernetes deployment scripts/ Keycloak seeding (seed-keycloak.py — users, groups, memberships, sponsors) ``` ## Running Locally ```bash ./dev.sh # Starts backend (:5001) + frontend (:5173) + Keycloak (:8180) locally ./demo.sh # Same as above but everything in containers (no local deps needed) ``` Login: `admin` / `admin`, or any seed user via "Sign in with Keycloak" (password: `password`) ## Building & Deploying ```bash ./build.sh # Builds Docker containers helm install osa ./chart/osa-suite --set secrets.fernetKey=... --set secrets.secretKey=... ``` ## Development Commands ```bash # Backend cd backend && source .venv/bin/activate && python run.py # Frontend cd frontend && npm run dev # Dev server cd frontend && npm run build # Production build (also runs tsc) ``` ## Architecture Conventions ### Backend - Flask blueprints: one per domain (auth, projects, users, licenses, certs, dashboard, downtime, feedback, settings) - All API routes prefixed with `/api/` - All list endpoints return wrapped objects: `{"projects": [...]}`, `{"licenses": [...]}`, `{"feedback": [...]}`, etc. - Single-item endpoints return: `{"project": {...}}`, `{"feedback": {...}}`, etc. - `@login_required` decorator on all routes except POST /api/auth/login and OIDC endpoints - `@project_access_required(min_privilege)` decorator enforces project-level permissions (read/write/admin) via session-cached Keycloak group memberships - `@admin_required` decorator on project creation, user create/update/delete - `/api/auth/login` and `/api/auth/me` return `is_admin` flag, `auth_method`, and `permissions` object - `/api/auth/refresh-permissions` re-fetches KC groups and updates session cache - OIDC endpoints: `/api/auth/oidc/available`, `/api/auth/oidc/login`, `/api/auth/oidc/callback` - OIDC callback derives permissions from KC groups via `keycloak_permissions.derive_permissions()` and caches in `session["kc_permissions"]` - Authlib OAuth client registered conditionally when `KEYCLOAK_OIDC_CLIENT_SECRET` is set - Group management API: `/api/projects//groups` (list/create/delete child groups), `/api/projects//groups//members` (list/add/remove members) - Member management API (KC-based): `/api/projects//members` (list/add/remove members from KC groups), `/api/projects//members//context` (other project memberships), `/api/projects//members//remove` (advanced removal with sponsorship actions) - Sponsorship API: `/api/projects//sponsor/` (POST to sponsor, DELETE to release) - Verification API: `POST /api/projects//verify` (updates `last_verified_at` and `last_verified_by`) - Cert passphrase API: `GET /api/certs//passphrase` (returns decrypted passphrase, separate from main cert endpoint for security) - Models use SQLAlchemy with PostgreSQL as the only supported database - No migrations directory — uses `db.create_all()` on startup with manual ALTER TABLE for schema changes in `run.py` - Audit logging via `log_audit()` on all CRUD and export operations - Cert private keys and passphrases encrypted at rest with Fernet; cert PEM stored unencrypted (public data) - License and cert status computed via hybrid properties, not stored columns. License status: archived → pending (no purchase date or future purchase date) → perpetual → expired → expiring_soon → active - Feedback screenshots stored as JSON array of base64 strings in the database - Downtime Tracker API: `/api/downtime` (list/create/update/delete). Fields: application, start_time, end_time, cause, lessons_learned, resolution, enclave (IL5/IL6 comma-separated), scope (disabled/limited), planned (boolean). Search spans application, cause, lessons_learned, resolution, submitted_by. - Settings stored as key-value pairs via the Setting model (expiry thresholds, notification config, sponsorship, work hours, etc.) - Work hours settings: `work_hours_start`, `work_hours_end`, `work_hours_timezone` — used by downtime tracker to classify events as during/after work hours - Email notifications via SMTP (configurable per-alert recipients, disabled when SMTP_HOST not set) ### Frontend - React Query (TanStack Query) for all server state — no Redux/Zustand - TanStack Table for all data tables via shared `DataTable` component - shadcn/ui components in `components/ui/` — installed via CLI, do not hand-edit - Hooks pattern: `use-projects.ts` exports `useProjects()`, `useCreateProject()`, etc. - `use-project-members.ts` exports `useProjectMembers()`, `useMemberRemovalContext()`, `useRemoveMember()`, `useSponsorUser()`, `useReleaseSponsor()` - Axios client in `lib/api.ts` with `withCredentials: true` and 401 interceptor - Vite proxies `/api` to backend in dev; nginx proxies in production - Toast notifications (sonner) for all mutations - Status badges color-coded: green (active/valid), amber (expiring_soon), red (expired), indigo (perpetual), blue (onboarding) - Feedback status badges: blue (unread), gray (read), amber (assigned), green (addressed) - Sponsor status badges on project detail: green (sponsored here), blue (sponsored elsewhere), red (unsponsored with deletion countdown) - Admin sidebar link (Settings icon) visible only when `user.is_admin` is true - Shared `ActivityFeed` component used on project detail and user detail pages - Project memberships editable inline via Select dropdowns for billing type on both project detail and user detail pages - Keycloak group management UI on project detail pages (split-panel: groups with app filter on left, members on right) - Members tab on project detail shows KC-based members with sponsor status and action buttons (sponsor/release/remove) - Cert form includes passphrase reveal button (fetches decrypted passphrase on demand) - Dashboard stats overview includes sponsorship metrics (sponsored count, unsponsored count with warning badge) - Downtime Tracker page with filterable table (application, enclave, scope, planned), work-hours classification column (during/after based on configurable work hours settings), and inline create/edit/delete - Admin settings page uses unified `SettingRow` layout across grouped cards (Thresholds & Scheduling, Project & User Defaults, Security) ### Naming - Backend: snake_case for Python, kebab-case for URL paths - Frontend: PascalCase for components, camelCase for hooks/utils, kebab-case for filenames - Database columns: snake_case, matching the JSON API field names exactly ## Key Design Decisions - **Database** — PostgreSQL is required. Helm chart supports bundled Postgres or external Postgres (e.g., RDS). Local dev uses a Postgres container started by `dev.sh`. Tests use in-memory SQLite for speed. - **Dual auth: local + Keycloak OIDC** — hardcoded admin/admin always works. When `KEYCLOAK_OIDC_CLIENT_SECRET` is set, a "Sign in with Keycloak" button appears on the login page. OIDC callback finds-or-creates a local User by `keycloak_id` then email. - **Admin access** — determined by `VELA-mgmt-admin` Keycloak group membership or hardcoded admin username. Controls sidebar admin link visibility. - **Permission model** — Keycloak child groups (`{PROJECT}-mgmt-read/write/admin`) replace the old `ProjectUser.privileges` column. Permissions are derived at login from KC group memberships and cached in `session["kc_permissions"]`. VELA group membership grants global escalation: VELA read = view all, VELA write = edit all, VELA admin = full admin. Hardcoded admin bypasses all checks. - **Keycloak group hierarchy** — Top-level groups per project (e.g., `ACME`). Child groups per app and permission: `{PROJECT}-{app}-{level}` (e.g., `ACME-bitbucket-read`, `ACME-mgmt-write`). Standard apps: bitbucket, srm, coverity, mgmt — each with read/write/admin. Custom child groups also supported. - **Sponsorship model** — Keycloak `sponsor` attribute (single-valued) on each user stores the project key that sponsors them. Local `User.unsponsored_since` tracks when sponsorship was cleared. Configurable `unsponsored_deletion_days` setting (default 7) controls the deletion countdown. Sponsorship release disables the user in KC, marks `unsponsored_since`, and sends notification email. - **Billing types** — `core`, `collaborator`, `standing` on ProjectUser. Business rule: users can only be `core` on one project (enforced at route level with conflict warnings and confirmation flow). - **Users are Keycloak-only** — Users are seeded exclusively in Keycloak via `scripts/seed-keycloak.py`. The backend `seed.py` only seeds projects, licenses, and certs into the local DB. Local `User` records are created on first OIDC login. The KC seeder assigns users to mgmt child groups plus random additional app child groups (bitbucket, srm, coverity) with deterministic randomness. - **Auto-assignment** — Adding a user to a project auto-adds them to the `{PROJECT}-mgmt-read` KC child group. Removing a user removes them from all child groups. Creating a project auto-creates all standard child groups in KC. - **Member removal workflow** — Three actions available: `remove_permissions` (KC groups only), `release_sponsorship` (clear sponsor, disable in KC, start deletion countdown), `release_and_remove` (both). Context endpoint provides other project memberships for informed decisions. - **Stale KC ID healing** — When adding a member to a group, if the stored `keycloak_id` is stale (404), the system auto-looks up the user by email in KC and updates the stored ID. - **Row-level filtering** — `Project.for_user()` returns only projects the user is a member of (based on KC `visible_projects` from session cache, or all if global admin / hardcoded admin). Licenses and dashboard stats scoped similarly. Certificates are global (not project-scoped). - **Project verification** — Projects have `last_verified_at` and `last_verified_by` fields for compliance/audit tracking, updated via verify endpoint. - **Project metadata** — Projects have `service`, `agency`, `cost_center` fields, contact fields with email subfields (bfm, pm, admin), and a configurable `rate` field (default 300). - **Feedback system** — users submit feedback from project detail pages with optional screenshots (auto-scaled to 1200x900 max). Admins manage via admin page with status workflow (unread → read → assigned → addressed). - **Configurable expiry thresholds** — "expiring soon" window for licenses and certs is configurable via admin settings (stored in settings table), not hardcoded. - **Email notifications** — SMTP-based with per-alert recipient configuration. Supports notifications for license/cert expiry, new feedback, user changes, and sponsorship release. Disabled when `SMTP_HOST` is not set. - **Cert encryption** — Fernet key persists to `.fernet_key` file in dev, env var in production. Changing the key makes existing encrypted data unrecoverable. - **Cert passphrase storage** — PKCS12 import passphrases optionally stored encrypted (Fernet) in `passphrase_encrypted` column. Retrieved via separate endpoint for security. PKCS12 export still without passphrase (explicit product decision). - **Downtime Tracker** — Logs application outage events with start/end times, cause, resolution, and lessons learned. Enclave field supports multi-select (IL5, IL6) stored as comma-separated string, serialized as array in API. Scope (disabled/limited) and planned (boolean) classify the nature of outages. Work-hours classification computed client-side using `date-fns-tz` against configurable work hours settings. - **Backend replicas** — scale freely with Postgres. ## Stubs (Not Yet Implemented) - `components/certs/aws-secrets-stub.tsx` — AWS Secrets Manager sync ## Security Notes - Never commit `.env`, `.fernet_key`, `.keycloak-secret`, `.keycloak-oidc-secret`, or `*.db` files - `SECRET_KEY` uses random bytes in dev; must be set via env var in production for session persistence - All error responses use generic messages; details logged server-side only - File uploads validated by extension allowlist (licenses) and size limit (50MB global) - Security headers set via `@app.after_request`: nosniff, frame deny, referrer policy - Cert passphrases and private keys encrypted at rest; decrypted only via dedicated endpoints