Login is now by email, not username. username still exists internally
(JWT sub claim, audit logs, Jira actor attribution, SSO provisioning all
still key off it) but is now always kept equal to email everywhere a user
is created or their email changes — never a separately-chosen value.
- User.email is now unique + NOT NULL (migration b067 backfills any
missing/blank email from username first, so existing rows — notably
the seeded admin, which historically had none — never violate it).
- /auth/login and the (unused but updated for consistency)
authenticate_user() now query by email.
- create_user (legacy, unreferenced but kept) and
create_user_without_password both derive username from email.
- update_user keeps username in sync when email changes, and rejects
duplicate emails.
- seed.py reads ADMIN_EMAIL (new env var, wired through install.sh and
docker-compose.prod.yml) for the initial admin; falls back to an
email-shaped ADMIN_USERNAME or a placeholder that's flagged for the
operator to fix.
- admin_config.py's import bundle now matches/creates users by email,
skipping (not crashing on) entries with no email.
- sso_service.py always sets username = email for SSO-provisioned users.
- LoginPage/auth.ts updated to email input/copy (wire field name stays
'username' — that's the OAuth2PasswordRequestForm spec, not the value).
Admins now create users with a name + email (no password) and role;
a Send Email action (per-user, also reusable for resets) issues a
one-time token and POSTs it to an admin-configurable webhook URL —
intended for a Power Automate flow that delivers the actual email.
The old admin-sets-the-password flow is kept server-side
(LegacyUserCreateWithPassword) but no longer wired into the UI.
Every user-facing surface (top bar, sidebar, user management, audit
log, assignee pickers, dispute notifications) now shows full_name
instead of username, falling back to username when unset.
Also fixes long attack_procedure/expected_detection/suggested_text
text overflowing its container in the template review panels and
Red/Blue team fields (missing break-words on whitespace-pre-wrap
blocks).
- /auth/refresh now allows a short grace window past expiry and checks
the blacklist, so an active session's silent refresh no longer fails
the instant its own token expires
- normal manager /approve flow now creates Jira tickets for the campaign
and its already-linked tests, matching the admin-only /activate path
- GenerateFromActorPayload now accepts start_date and threads it through
to the new campaign instead of silently discarding it
- Single admin Jira account stored in system_configs (jira.admin_email, jira.admin_api_token)
- Admin Tempo token in system_configs (tempo.admin_token)
- get_admin_jira_client() + has_admin_jira_configured() replace per-user auth in all lifecycle hooks
- lookup_user_jira_account_id() auto-discovers each user's Atlassian accountId by email on login
- auto_log_test_worklog() now uses admin Tempo token and logs both red+blue team time
- Settings > Jira: admin credentials fields + test buttons moved to admin Jira tab
- Profile section simplified: jira_account_id shown read-only (auto-detected)
Snyk scan found 3 High severity vulns: two in ecdsa (pulled by python-jose)
and one in diskcache (pulled by pySigma, never imported). Remove both
vulnerable dependencies and migrate JWT handling to PyJWT. Fix
test_logout_revokes_token which broke because test stubs sys.modules[jose]
with a MagicMock at collection time; test now uses PyJWT directly.
Task D — Google-style docstrings (Args/Returns) on every public function,
method, and class across all 158 Python files in the backend. Zero ruff D
violations (pydocstyle Google convention).
Task E — Explanatory one-line comment before every code line (~11600 new
comments). ruff check passes clean after isort re-sort.
Problem: 15-minute tokens with no refresh mechanism kicked users to login
even when actively using the app.
Fixes:
1. config.py: raise ACCESS_TOKEN_EXPIRE_MINUTES from 15 → 480 (8h).
Reasonable for an enterprise internal tool; still configurable via env.
2. POST /auth/refresh: new endpoint that reads the current aegis_token
cookie and issues a fresh token if the session is still valid. Returns
the new token in the cookie + body (same shape as /auth/login).
3. frontend/api/client.ts: response interceptor now attempts a silent
refresh on 401 before redirecting to login:
- Calls POST /auth/refresh once per failed request
- If refresh succeeds: retries the original request transparently
- If refresh fails: redirects to /login as before
- Deduplicates concurrent refresh attempts (refresh once, resolve all)
- Never attempts refresh on /auth/refresh or /auth/login themselves
- auth: desacopla SECURE_COOKIES de AEGIS_ENV para que el login
funcione sobre HTTP (SECURE_COOKIES=false en servidor local)
- TechniqueCell: button -> Link para href real (right-click, a11y)
- TechniquesPage: añade Link en celda MITRE ID en vista lista
- nginx CSP: amplía connect-src con ws:/wss: para evitar bloqueos
Revoke tokens by jti in a dedicated Redis DB, honor TTL from JWT exp on logout, reject revoked tokens in get_current_user, and add FakeRedis-backed API tests.
- Add must_change_password field to User model with migration b023
- Add POST /auth/change-password endpoint with password policy validation
- Add require_password_changed dependency to block requests until password is changed
- Add ChangePasswordModal with live password policy checklist (forced on first login)
- Show password policy in CreateUserModal and EditUserModal
- Fix backend permissions: tests, campaigns, templates, reports, evidence, worklogs
- red_tech/blue_tech: execute only, cannot create tests/campaigns/templates
- red_lead/blue_lead: create/edit tests/campaigns/templates, generate reports, no system access
- viewer: read-only everywhere, can generate reports
- Fix frontend role checks across TestDetailPage, TestDetailHeader, TeamTabs, TestsPage, CampaignsPage, CampaignDetailPage, Sidebar
Foundational changes required before any new feature work can begin.
- 0.1 Redis infrastructure: add redis:7-alpine to docker-compose dev and prod,
REDIS_URL config, singleton client in app/infrastructure/redis_client.py
- 0.2 Token blacklist on Redis SEC-001: replace in-memory dict with Redis SETEX
keyed by jti, auto-expiring TTL derived from token exp
- 0.3 Database indexes SR-006: Alembic migration b019 with 5 composite indexes
for scoring, MTTD/MTTR, remediation, and notification queries
- 0.4 Domain exceptions TD-003: app/domain/exceptions.py with typed errors,
error_handler middleware mapping them to HTTP, services decoupled from FastAPI
- 0.5 Fix silenced exceptions TD-007: replace 4 bare except-pass blocks in
test_workflow_service with logger.warning with exc_info
- 0.6 CI pipeline TD-009: GitHub Actions workflow with Postgres and Redis
service containers, ruff lint, pytest; ruff.toml for baseline config
Critical (1-3):
- Replace hardcoded admin credentials with secure auto-generation (seed.py)
- Enforce SECRET_KEY configuration, fail in production if missing (config.py)
- Add Zip Slip and Zip Bomb protection to all ZIP import services
High/Medium (4-9):
- Add 50MB file size limit and extension whitelist to evidence uploads
- Configure CORS origins via environment variable instead of hardcoded
- Migrate JWT storage from localStorage to HttpOnly cookies (frontend+backend)
- Add rate limiting (5/min) on login endpoint via slowapi
- Replace generic dict payloads with Pydantic schemas (mass assignment)
Medium (10-17):
- Check is_active on login to prevent disabled users from authenticating
- Sanitize exception messages in API responses (system, data_sources)
- Escape LIKE wildcards in all ilike search filters across 8 routers
- Run Docker container as non-root user (appuser)
- Make MINIO_SECURE configurable via environment variable
- Add password complexity policy (12+ chars, upper/lower/digit/special)
- Implement JWT token revocation via in-memory blacklist + reduce TTL to 15min
- Replace xml.etree with defusedxml to prevent Billion Laughs attacks
Low (18-20):
- Add security headers to Nginx (CSP, X-Frame-Options, HSTS-ready, etc.)
- Disable Swagger UI/ReDoc/OpenAPI in production
- Restrict /health endpoint to internal networks via Nginx ACL
Also: rewrite install.sh as interactive wizard for guided deployment,
fix test-from-template validation error (technique_id UUID vs MITRE ID)