mirror of
https://github.com/azaion/admin.git
synced 2026-06-21 12:01:10 +00:00
491993f9c1
AZ-536 — replace unsalted SHA-384 password hashing with Argon2id (RFC 9106). Stored as PHC string with 64 MiB / 3 iter / 1 lane defaults; legacy SHA-384 hashes detected by prefix and lazily re-hashed on next successful login. Verify uses CryptographicOperations.FixedTimeEquals on both formats. AZ-537 — add per-IP sliding window rate limit on /login (ASP.NET Core RateLimiter, 10/60s default — production-tight) plus DB-backed per-account limit (5/300s) and consecutive-failure lockout (10 / 15 min) on the users row. Adds a generic audit_events table with INSERT/SELECT-only grants for the app role so the per-account count is queryable and admins cannot erase their own forensic trail. BusinessExceptionHandler maps AccountLocked to 423 and LoginRateLimited to 429, both with Retry-After. AZ-538 — drop the http://admin.azaion.com origin from CORS, gate UseHsts() + UseHttpsRedirection() to non-Development envs (1y / preload). Test infra: Npgsql in the e2e project + a DbHelper for direct DB inspection used by the AZ-536/537 ACs. appsettings.Development.json raises PerIpPermitLimit to 1000 so the suite (~270 logins from one container IP) doesn't false-trip the limiter. Tests: 53 pass + 3 documented skips (per-IP rate limit needs distinct client IPs; HSTS/HTTPS redirect need ASPNETCORE_ENVIRONMENT=Production). Code review: PASS_WITH_WARNINGS — 0 Critical, 0 High, 1 Medium, 3 Low. See _docs/03_implementation/reviews/batch_01_cycle2_review.md. Closes AZ-530 epic batch 1 of 4. Co-authored-by: Cursor <cursoragent@cursor.com>
27 lines
1.1 KiB
SQL
27 lines
1.1 KiB
SQL
-- AZ-537 (Epic AZ-530, CMMC AC.L2-3.1.8): account lockout + audit events.
|
|
-- Adds the per-row state used by UserService.ValidateUser to enforce a 10-failure
|
|
-- consecutive-attempt lockout, plus a generic audit_events table that the per-account
|
|
-- sliding-window rate-limit reads. The audit table is also reused by future security
|
|
-- events (login_success, lockout_release, etc.).
|
|
|
|
ALTER TABLE public.users
|
|
ADD COLUMN IF NOT EXISTS failed_login_count int NOT NULL DEFAULT 0,
|
|
ADD COLUMN IF NOT EXISTS lockout_until timestamp NULL;
|
|
|
|
CREATE TABLE IF NOT EXISTS public.audit_events
|
|
(
|
|
id bigserial PRIMARY KEY,
|
|
event_type varchar(64) NOT NULL,
|
|
occurred_at timestamp NOT NULL DEFAULT now(),
|
|
email varchar(160) NULL,
|
|
ip varchar(64) NULL,
|
|
metadata text NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS audit_events_event_type_email_idx
|
|
ON public.audit_events (event_type, email, occurred_at DESC);
|
|
|
|
GRANT INSERT, SELECT ON public.audit_events TO azaion_admin;
|
|
GRANT SELECT ON public.audit_events TO azaion_reader;
|
|
GRANT USAGE, SELECT ON SEQUENCE public.audit_events_id_seq TO azaion_admin;
|