8f09e0e81a
Rename Go client package from mciasgoclient to mcias - Update package declaration in client.go - Update error message strings to reference new package name - Update test package and imports to use new name - Update README.md documentation and examples with new package name - All tests pass
Kyle Isom2026-03-14 19:01:07 -07:00
89f78a38dd
Update web UI to support all compile-time roles - Update knownRoles to include guest, viewer, editor, and commenter - Replace hardcoded role strings with model constants - Remove obsolete 'service' role from UI - All tests pass
Kyle Isom2026-03-12 21:14:22 -07:00
4d6c5cb67c
Add guest, viewer, editor, and commenter roles to compile-time allowlist - Add RoleGuest, RoleViewer, RoleEditor, and RoleCommenter constants - Update allowedRoles map to include new roles - Update ValidateRole error message with complete role list - All tests pass; build verified
v1.3.0
Kyle Isom2026-03-12 21:03:24 -07:00
f880bbb6de
Add granular role grant/revoke endpoints to REST and gRPC APIs - Add POST /v1/accounts/{id}/roles and DELETE /v1/accounts/{id}/roles/{role} REST endpoints - Add GrantRole and RevokeRole RPCs to AccountService in gRPC API - Update OpenAPI specification with new endpoints - Add grant and revoke subcommands to mciasctl - Add grant and revoke subcommands to mciasgrpcctl - Regenerate proto files with new message types and RPCs - Implement gRPC server methods for granular role management - All existing tests pass; build verified with goimports Security: Role changes are audited via EventRoleGranted and EventRoleRevoked events, consistent with existing SetRoles implementation.
Kyle Isom2026-03-12 20:55:49 -07:00
833775de83
db: integrate golang-migrate for schema migrations - internal/db/migrations/: five embedded SQL files containing the migration SQL previously held as Go string literals. Files follow the NNN_description.up.sql naming convention required by golang-migrate's iofs source. - internal/db/migrate.go: rewritten to use github.com/golang-migrate/migrate/v4 with the database/sqlite driver (modernc.org/sqlite, pure Go) and source/iofs for compile-time embedded SQL. - newMigrate() opens a dedicated *sql.DB so m.Close() does not affect the caller's shared connection. - Migrate() includes a compatibility shim: reads the legacy schema_version table and calls m.Force(v) before m.Up() so existing databases are not re-migrated. - LatestSchemaVersion promoted from var to const. - internal/db/db.go: added path field to DB struct; Open() translates ':memory:' to a named shared-cache URI (file:mcias_N?mode=memory&cache=shared) so the migration runner can open a second connection to the same in-memory database without sharing the handle that golang-migrate will close on teardown. - go.mod: added golang-migrate/migrate/v4 v4.19.1 (direct). All callers unchanged. All tests pass; golangci-lint clean.
Kyle Isom2026-03-12 11:52:39 -07:00
562aad908e
UI: pgcreds create button; show logged-in user
Kyle Isom2026-03-12 11:38:57 -07:00
fdcc117c89
Fix UI: install real HTMX, add PG creds and roles UI - web/static/htmx.min.js: replace placeholder stub with htmx 2.0.4 (downloaded from unpkg.com). The placeholder only logged a console warning; no HTMX features worked, so form submissions fell back to native POSTs and the account_row fragment was returned as a raw HTML body rather than spliced into the table. This was the root cause of account creation appearing to 'do nothing'. - internal/ui/ui.go: add pgcreds_form.html to shared template list; add PUT /accounts/{id}/pgcreds route; reorder AccountDetailData fields so embedded PageData does not shadow Account. - internal/ui/handlers_accounts.go: add handleSetPGCreds handler — encrypts the submitted password with AES-256-GCM using the server master key before storage, validates system-account-only constraint, re-reads and re-renders the fragment after save. Add PGCred field population to handleAccountDetail. - internal/ui/ui_test.go: add tests for account creation, role management, and PG credential handlers. - web/templates/account_detail.html: add Postgres Credentials card for system accounts. - web/templates/fragments/pgcreds_form.html: new fragment for the PG credentials form; CSRF token is supplied via the body-level hx-headers attribute in base.html. Security: PG password is encrypted with AES-256-GCM (crypto.SealAESGCM) before storage; a fresh nonce is generated per call; the plaintext is never logged or returned in responses.
Kyle Isom2026-03-11 22:30:13 -07:00
b495a90a9d
Fix F-08, F-13: Adjust lockout expiration logic and enforce password length in tests
Kyle Isom2026-03-11 21:36:04 -07:00
2dbc553abe
Fix F-07: pre-compute real Argon2 dummy hash via sync.Once - auth/auth.go: add DummyHash() which uses sync.Once to compute HashPassword("dummy-password-for-timing-only", DefaultArgonParams()) on first call; subsequent calls return the cached PHC string; add sync to imports - auth/auth_test.go: TestDummyHashIsValidPHC verifies the hash parses and verifies correctly; TestDummyHashIsCached verifies sync.Once behaviour; TestDummyHashMatchesDefaultParams verifies embedded m/t/p match DefaultArgonParams() - server/server.go, grpcserver/auth.go, ui/ui.go: replace five hardcoded PHC strings with auth.DummyHash() calls - AUDIT.md: mark F-07 as fixed Security: the previous hardcoded hash used a 6-byte salt and 6-byte output ("testsalt"/"testhash" in base64), which Argon2id verifies faster than a real 16-byte-salt / 32-byte-output hash. This timing gap was measurable and could aid user enumeration. auth.DummyHash() uses identical parameters and full-length salt and output, so dummy verification timing matches real timing exactly, regardless of future parameter changes.
Kyle Isom2026-03-11 20:37:27 -07:00
06ec8be1c9
Fix F-16: revoke old system token before issuing new one - ui/handlers_accounts.go (handleIssueSystemToken): call GetSystemToken before issuing; if one exists, call RevokeToken(existing.JTI, "rotated") before TrackToken and SetSystemToken for the new token; mirrors the pattern in REST handleTokenIssue and gRPC IssueServiceToken - db/db_test.go: TestSystemTokenRotationRevokesOld verifies the full rotation flow: old JTI revoked with reason "rotated", new JTI tracked and active, GetSystemToken returns the new JTI - AUDIT.md: mark F-16 as fixed Security: without this fix an old system token remained valid after rotation until its natural expiry, giving a leaked or stolen old token extra lifetime. With the revocation the old JTI is immediately marked in token_revocation so any validator checking revocation status rejects it.
Kyle Isom2026-03-11 20:34:57 -07:00
e20b66d6f6
Fix F-02: replace password-in-hidden-field with nonce - ui/ui.go: add pendingLogin struct and pendingLogins sync.Map to UIServer; add issueTOTPNonce (generates 128-bit random nonce, stores accountID with 90s TTL) and consumeTOTPNonce (single-use, expiry-checked LoadAndDelete); add dummyHash() method - ui/handlers_auth.go: split handleLoginPost into step 1 (password verify → issue nonce) and step 2 (handleTOTPStep, consume nonce → validate TOTP) via a new finishLogin helper; password never transmitted or stored after step 1 - ui/ui_test.go: refactor newTestMux to reuse new newTestUIServer; add TestTOTPNonceIssuedAndConsumed, TestTOTPNonceUnknownRejected, TestTOTPNonceExpired, and TestLoginPostPasswordNotInTOTPForm; 11/11 tests pass - web/templates/fragments/totp_step.html: replace 'name=password' hidden field with 'name=totp_nonce' - db/accounts.go: add GetAccountByID for TOTP step lookup - AUDIT.md: mark F-02 as fixed Security: the plaintext password previously survived two HTTP round-trips and lived in the browser DOM during the TOTP step. The nonce approach means the password is verified once and immediately discarded; only an opaque random token tied to an account ID (never a credential) crosses the wire on step 2. Nonces are single-use and expire after 90 seconds to limit the window if one is captured.
Kyle Isom2026-03-11 20:33:04 -07:00
0e201ae05b
Fix F-03: make token renewal atomic - 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.
Kyle Isom2026-03-11 20:24:32 -07:00
c8f1ac6dac
Fix F-01: TOTP enroll must not set required=1 early - db/accounts.go: add StorePendingTOTP() which writes totp_secret_enc and totp_secret_nonce but leaves totp_required=0; add comment explaining two-phase flow - server.go (handleTOTPEnroll): switch from SetTOTP() to StorePendingTOTP() so the required flag is only set after the user confirms a valid TOTP code via handleTOTPConfirm, which still calls SetTOTP() - server_test.go: TestTOTPEnrollDoesNotRequireTOTP verifies that after POST /v1/auth/totp/enroll, TOTPRequired is false and the encrypted secret is present; confirms that a subsequent login without a TOTP code still succeeds (no lockout) - AUDIT.md: mark F-01 and F-11 as fixed Security: without this fix an admin who enrolls TOTP but abandons before confirmation is permanently locked out because totp_required=1 but no confirmed secret exists. StorePendingTOTP() keeps the secret pending until the user proves possession by confirming a valid code.
Kyle Isom2026-03-11 20:18:57 -07:00
47847a4312
Fix F-04 + F-11; add AUDIT.md - 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)
Kyle Isom2026-03-11 20:18:09 -07:00
f2903ca103
Fix grpcserver rate limiter: move to Server field
Kyle Isom2026-03-11 19:20:32 -07:00
4d140886ca
Add HTMX-based UI templates and handlers for account and audit management
Kyle Isom2026-03-11 18:02:53 -07:00