Files
steward/internal/model/auth.go
T
abrown38andClaude Opus 5.5 8a69038ab5 Admin name at init, token rotation, read-only forms, token role presets
- stewardd init asks for the first administrator's name on a terminal.
- Tokens can be rotated (steward token rotate, POST /tokens/{id}/rotate,
  Rotate in the web UI), with a grace period for the old secret, as in
  Jarl. Schema migration 2 adds the token columns.
- Read-only users see the settings and a subnet's settings locked,
  instead of editable fields without a Save button.
- The New token dialog only offers roles within what the user may do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:42:37 +01:00

139 lines
4.3 KiB
Go

package model
import (
"encoding/json"
"time"
)
// Grant gives access to one resource (see internal/auth).
type Grant struct {
Resource string `json:"resource"` // config, leases, audit, *
Access string `json:"access"` // read or write (write implies read)
}
// User is a person or a service account.
type User struct {
ID int64 `json:"id"`
Username string `json:"username"`
PasswordHash string `json:"-"`
HasPassword bool `json:"has_password"`
Admin bool `json:"admin"`
Grants []Grant `json:"grants"`
Disabled bool `json:"disabled"`
Comment string `json:"comment"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
LastLoginAt *time.Time `json:"last_login_at,omitempty"`
}
// Token is an API token. The secret is shown once, when it is made; only
// its hash is stored.
type Token struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Username string `json:"username,omitempty"`
Name string `json:"name"`
PublicID string `json:"public_id"`
SecretHash string `json:"-"`
// Grants restrict the token to part of its user's permissions; nil
// means all of them.
Grants []Grant `json:"grants"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
CreatedAt time.Time `json:"created_at"`
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
LastUsedIP string `json:"last_used_ip,omitempty"`
RevokedAt *time.Time `json:"revoked_at,omitempty"`
// RotatedAt is when the token last got a new secret (nil: never).
RotatedAt *time.Time `json:"rotated_at,omitempty"`
// After a rotation with a grace period, the previous secret keeps
// working until PrevExpiresAt.
PrevSecretHash string `json:"-"`
PrevExpiresAt *time.Time `json:"previous_secret_valid_until,omitempty"`
}
// IssuedAt is when the token's current secret was made: its last
// rotation, or its creation.
func (t *Token) IssuedAt() time.Time {
if t.RotatedAt != nil {
return *t.RotatedAt
}
return t.CreatedAt
}
// Session is a web UI login.
type Session struct {
IDHash string `json:"-"`
UserID int64 `json:"user_id"`
CSRFToken string `json:"-"`
CreatedAt time.Time `json:"created_at"`
ExpiresAt time.Time `json:"expires_at"`
LastSeenAt time.Time `json:"last_seen_at"`
IP string `json:"ip"`
UserAgent string `json:"user_agent"`
}
// AuditEvent records an action: logins, users and tokens, changes, lease
// deletions.
type AuditEvent struct {
ID int64 `json:"id"`
Time time.Time `json:"time"`
UserID int64 `json:"user_id,omitempty"`
Username string `json:"username,omitempty"`
Auth string `json:"auth,omitempty"` // session, token, socket
TokenID int64 `json:"token_id,omitempty"`
IP string `json:"ip,omitempty"`
Action string `json:"action"`
Object string `json:"object,omitempty"`
Result string `json:"result"` // success, failure, denied
Detail string `json:"detail,omitempty"`
}
// Object kinds of changes.
const (
KindSettings = "settings"
KindSubnet = "subnet"
KindReservation = "reservation"
)
// Change is one object's change: Before is nil for a create, After is nil
// for a delete. Settings have ID 0.
type Change struct {
Kind string `json:"kind"`
ID int64 `json:"id"`
Label string `json:"label"`
Before json.RawMessage `json:"before,omitempty"`
After json.RawMessage `json:"after,omitempty"`
}
// Action describes a change in a word.
func (c Change) Action() string {
switch {
case c.Before == nil:
return "create"
case c.After == nil:
return "delete"
}
return "update"
}
// Inverse is the change that undoes c.
func (c Change) Inverse() Change {
c.Before, c.After = c.After, c.Before
return c
}
// HistoryEntry is a saved configuration change.
type HistoryEntry struct {
ID int64 `json:"id"`
Time time.Time `json:"time"`
UserID int64 `json:"user_id,omitempty"`
Username string `json:"username"`
Via string `json:"via"` // web, cli, api
Description string `json:"description"`
Changes []Change `json:"changes"`
// RevertOf is the entry this one undid; RevertedBy the entry that
// undid this one.
RevertOf int64 `json:"revert_of,omitempty"`
RevertedBy int64 `json:"reverted_by,omitempty"`
}