Implement dashboard and audit log templates, add paginated audit log support
- 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.
This commit is contained in:
@@ -29,47 +29,40 @@ const (
|
||||
// Fields containing credential material (PasswordHash, TOTPSecretEnc) are
|
||||
// never serialised into API responses — callers must explicitly omit them.
|
||||
type Account struct {
|
||||
ID int64 `json:"-"`
|
||||
UUID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
AccountType AccountType `json:"account_type"`
|
||||
Status AccountStatus `json:"status"`
|
||||
TOTPRequired bool `json:"totp_required"`
|
||||
|
||||
// PasswordHash is a PHC-format Argon2id string. Never returned in API
|
||||
// responses; populated only when reading from the database.
|
||||
PasswordHash string `json:"-"`
|
||||
|
||||
// TOTPSecretEnc and TOTPSecretNonce hold the AES-256-GCM-encrypted TOTP
|
||||
// shared secret. Never returned in API responses.
|
||||
TOTPSecretEnc []byte `json:"-"`
|
||||
TOTPSecretNonce []byte `json:"-"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
DeletedAt *time.Time `json:"deleted_at,omitempty"`
|
||||
UUID string `json:"id"`
|
||||
Username string `json:"username"`
|
||||
AccountType AccountType `json:"account_type"`
|
||||
Status AccountStatus `json:"status"`
|
||||
PasswordHash string `json:"-"`
|
||||
TOTPSecretEnc []byte `json:"-"`
|
||||
TOTPSecretNonce []byte `json:"-"`
|
||||
ID int64 `json:"-"`
|
||||
TOTPRequired bool `json:"totp_required"`
|
||||
}
|
||||
|
||||
// Role is a string label assigned to an account to grant permissions.
|
||||
type Role struct {
|
||||
GrantedAt time.Time `json:"granted_at"`
|
||||
GrantedBy *int64 `json:"-"`
|
||||
Role string `json:"role"`
|
||||
ID int64 `json:"-"`
|
||||
AccountID int64 `json:"-"`
|
||||
Role string `json:"role"`
|
||||
GrantedBy *int64 `json:"-"`
|
||||
GrantedAt time.Time `json:"granted_at"`
|
||||
}
|
||||
|
||||
// TokenRecord tracks an issued JWT by its JTI for revocation purposes.
|
||||
// The raw token string is never stored — only the JTI identifier.
|
||||
type TokenRecord struct {
|
||||
ID int64 `json:"-"`
|
||||
JTI string `json:"jti"`
|
||||
AccountID int64 `json:"-"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
IssuedAt time.Time `json:"issued_at"`
|
||||
RevokedAt *time.Time `json:"revoked_at,omitempty"`
|
||||
RevokeReason string `json:"revoke_reason,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
RevokedAt *time.Time `json:"revoked_at,omitempty"`
|
||||
JTI string `json:"jti"`
|
||||
RevokeReason string `json:"revoke_reason,omitempty"`
|
||||
ID int64 `json:"-"`
|
||||
AccountID int64 `json:"-"`
|
||||
}
|
||||
|
||||
// IsRevoked reports whether the token has been explicitly revoked.
|
||||
@@ -84,46 +77,40 @@ func (t *TokenRecord) IsExpired() bool {
|
||||
|
||||
// SystemToken represents the current active service token for a system account.
|
||||
type SystemToken struct {
|
||||
ID int64 `json:"-"`
|
||||
AccountID int64 `json:"-"`
|
||||
JTI string `json:"jti"`
|
||||
ExpiresAt time.Time `json:"expires_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
JTI string `json:"jti"`
|
||||
ID int64 `json:"-"`
|
||||
AccountID int64 `json:"-"`
|
||||
}
|
||||
|
||||
// PGCredential holds Postgres connection details for a system account.
|
||||
// The password is encrypted at rest; PGPassword is only populated after
|
||||
// decryption and must never be logged or included in API responses.
|
||||
type PGCredential struct {
|
||||
ID int64 `json:"-"`
|
||||
AccountID int64 `json:"-"`
|
||||
PGHost string `json:"host"`
|
||||
PGPort int `json:"port"`
|
||||
PGDatabase string `json:"database"`
|
||||
PGUsername string `json:"username"`
|
||||
|
||||
// PGPassword is plaintext only after decryption. Never log or serialise.
|
||||
PGPassword string `json:"-"`
|
||||
|
||||
// PGPasswordEnc and PGPasswordNonce are the AES-256-GCM ciphertext and
|
||||
// nonce stored in the database.
|
||||
PGPasswordEnc []byte `json:"-"`
|
||||
PGPasswordNonce []byte `json:"-"`
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
PGHost string `json:"host"`
|
||||
PGDatabase string `json:"database"`
|
||||
PGUsername string `json:"username"`
|
||||
PGPassword string `json:"-"`
|
||||
PGPasswordEnc []byte `json:"-"`
|
||||
PGPasswordNonce []byte `json:"-"`
|
||||
ID int64 `json:"-"`
|
||||
AccountID int64 `json:"-"`
|
||||
PGPort int `json:"port"`
|
||||
}
|
||||
|
||||
// AuditEvent represents a single entry in the append-only audit log.
|
||||
// Details must never contain credential material (passwords, tokens, secrets).
|
||||
type AuditEvent struct {
|
||||
ID int64 `json:"id"`
|
||||
EventTime time.Time `json:"event_time"`
|
||||
EventType string `json:"event_type"`
|
||||
ActorID *int64 `json:"-"`
|
||||
TargetID *int64 `json:"-"`
|
||||
EventType string `json:"event_type"`
|
||||
IPAddress string `json:"ip_address,omitempty"`
|
||||
Details string `json:"details,omitempty"` // JSON string; no secrets
|
||||
Details string `json:"details,omitempty"`
|
||||
ID int64 `json:"id"`
|
||||
}
|
||||
|
||||
// Audit event type constants — exhaustive list, enforced at write time.
|
||||
|
||||
Reference in New Issue
Block a user