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.
- Introduced `web/templates/` for HTMX-fragmented pages (`dashboard`, `accounts`, `account_detail`, `error_fragment`, etc.).
- Implemented UI routes for account CRUD, audit log display, and login/logout with CSRF protection.
- Added `internal/ui/` package for handlers, CSRF manager, session validation, and token issuance.
- Updated documentation to include new UI features and templates directory structure.
- Security: Double-submit CSRF cookies, constant-time HMAC validation, login password/Argon2id re-verification at all steps to prevent bypass.
- ARCHITECTURE.md §12: add mciasdb, mciasgrpcctl, internal/grpcserver,
proto/, and gen/ to the directory structure diagram
- ARCHITECTURE.md §17: replace buf generate references with protoc;
the implementation uses protoc + protoc-gen-go + protoc-gen-go-grpc
invoked via go generate ./... (proto/generate.go)
- PROJECT_PLAN.md §7.1: replace buf.yaml/buf.gen.yaml acceptance
criteria with the protoc-based go:generate approach actually used
- PROJECT_PLAN.md §7.6: clarify that gen/ is committed to the repo
(not gitignored); only the binary /mciasgrpcctl is excluded
- PROJECT.md: replace scrypt with Argon2id (the actual algorithm);
remove the redundant Argon2 suggestion line
- proto/mcias/v1/: AdminService, AuthService, TokenService,
AccountService, CredentialService; generated Go stubs in gen/
- internal/grpcserver: full handler implementations sharing all
business logic (auth, token, db, crypto) with REST server;
interceptor chain: logging -> auth (JWT alg-first + revocation) ->
rate-limit (token bucket, 10 req/s, burst 10, per-IP)
- internal/config: optional grpc_addr field in [server] section
- cmd/mciassrv: dual-stack startup; gRPC/TLS listener on grpc_addr
when configured; graceful shutdown of both servers in 15s window
- cmd/mciasgrpcctl: companion gRPC CLI mirroring mciasctl commands
(health, pubkey, account, role, token, pgcreds) using TLS with
optional custom CA cert
- internal/grpcserver/grpcserver_test.go: 20 tests via bufconn covering
public RPCs, auth interceptor (no token, invalid, revoked -> 401),
non-admin -> 403, Login/Logout/RenewToken/ValidateToken flows,
AccountService CRUD, SetPGCreds/GetPGCreds AES-GCM round-trip,
credential fields absent from all responses
Security:
JWT validation path identical to REST: alg header checked before
signature, alg:none rejected, revocation table checked after sig.
Authorization metadata value never logged by any interceptor.
Credential fields (PasswordHash, TOTPSecret*, PGPassword) absent from
all proto response messages — enforced by proto design and confirmed
by test TestCredentialFieldsAbsentFromAccountResponse.
Login dummy-Argon2 timing guard preserves timing uniformity for
unknown users (same as REST handleLogin).
TLS required at listener level; cmd/mciassrv uses
credentials.NewServerTLSFromFile; no h2c offered.
137 tests pass, zero race conditions (go test -race ./...)
- 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.