- db/accounts.go: add RenewToken(oldJTI, reason, newJTI,
accountID, issuedAt, expiresAt) which wraps RevokeToken +
TrackToken in a single BEGIN/COMMIT transaction; if either
step fails the whole tx rolls back, so the user is never
left with neither old nor new token valid
- server.go (handleRenewToken): replace separate RevokeToken +
TrackToken calls with single RenewToken call; failure now
returns 500 instead of silently losing revocation
- grpcserver/auth.go (RenewToken): same replacement
- db/db_test.go: TestRenewTokenAtomic verifies old token is
revoked with correct reason, new token is tracked and not
revoked, and a second renewal on the already-revoked old
token returns an error
- AUDIT.md: mark F-03 as fixed
Security: without atomicity a crash/error between revoke and
track could leave the old token active alongside the new one
(two live tokens) or revoke the old token without tracking
the new one (user locked out). The transaction ensures
exactly one of the two tokens is valid at all times.
- AUDIT.md: security audit report with 16 findings (F-01..F-16)
- F-04 (server.go): wire loginRateLimit (10 req/s, burst 10) to
POST /v1/auth/login and POST /v1/token/validate; no limit on
/v1/health or public-key endpoints
- F-04 (server_test.go): TestLoginRateLimited uses concurrent
goroutines (sync.WaitGroup) to fire burst+1 requests before
Argon2id completes, sidestepping token-bucket refill timing;
TestTokenValidateRateLimited; TestHealthNotRateLimited
- F-11 (ui.go): refactor Register() so all UI routes are mounted
on a child mux wrapped with securityHeaders middleware; five
headers set on every response: Content-Security-Policy,
X-Content-Type-Options, X-Frame-Options, HSTS, Referrer-Policy
- F-11 (ui_test.go): 7 new tests covering login page, dashboard
redirect, root redirect, static assets, CSP directives,
HSTS min-age, and middleware unit behaviour
Security: rate limiter on login prevents brute-force credential
stuffing; security headers mitigate clickjacking (X-Frame-Options
DENY), MIME sniffing (nosniff), and protocol downgrade (HSTS)
The package-level defaultRateLimiter drained its token bucket
across all test cases, causing later tests to hit ResourceExhausted.
Move rateLimiter from a package-level var to a *grpcRateLimiter field
on Server; New() allocates a fresh instance (10 req/s, burst 10) per
server. Each test's newTestEnv() constructs its own Server, so tests
no longer share limiter state.
Production behaviour is unchanged: a single Server is constructed at
startup and lives for the process lifetime.
- Added `web/templates/{dashboard,audit,base,accounts,account_detail}.html` for a consistent UI.
- Implemented new audit log endpoint (`GET /v1/audit`) with filtering and pagination via `ListAuditEventsPaged`.
- Extended `AuditQueryParams`, added `AuditEventView` for joined actor/target usernames.
- Updated configuration (`goimports` preference), linting rules, and E2E tests.
- No logic changes to existing APIs.
* Rewrite .golangci.yaml to v2 schema: linters-settings ->
linters.settings, issues.exclude-rules -> issues.exclusions.rules,
issues.exclude-dirs -> issues.exclusions.paths
* Drop deprecated revive exported/package-comments rules: personal
project, not a public library; godoc completeness is not a CI req
* Add //nolint:gosec G101 on PassphraseEnv default in config.go:
environment variable name is not a credential value
* Add //nolint:gosec G101 on EventPGCredUpdated in model.go:
audit event type string, not a credential
Security: no logic changes. gosec G101 suppressions are false
positives confirmed by code inspection: neither constant holds a
credential value.
- Add test/e2e: 11 end-to-end tests covering full login/logout,
token renewal, admin account management, credential-never-in-response,
unauthorised access, JWT alg confusion and alg:none attacks,
revoked token rejection, system account token issuance,
wrong-password vs unknown-user indistinguishability
- Apply gofmt to all source files (formatting only, no logic changes)
- Update .golangci.yaml for golangci-lint v2 (version field required,
gosimple merged into staticcheck, formatters section separated)
- Update PROGRESS.md to reflect Phase 5 completion
Security:
All 97 tests pass with go test -race ./... (zero race conditions).
Adversarial JWT tests (alg confusion, alg:none) confirm the
ValidateToken alg-first check is effective against both attack classes.
Credential fields (PasswordHash, TOTPSecret*, PGPassword) confirmed
absent from all API responses via both unit and e2e tests.
go vet ./... clean. golangci-lint v2.6.2 incompatible with go1.26
runtime; go vet used as linter until toolchain is updated.