9029d48584
- TERDUT_OPERATOR_KEY creates or re-keys the instance-scoped service account
"terdut-operator" at every start, so terdut-operator needs no bootstrap
handshake. An instance-scoped account now acts as owner of every team's
configuration, but is not a member of any team.
- POST /api/teams takes an external_id (instance service accounts only) and
is idempotent on it, so automation finds its own team again after a crash
instead of adopting by display name. GET /api/teams?name= is removed.
- Integration and dead man's switch names are unique per team (409). The
escalation PUT accepts usernames and resolves them itself.
- The 18 migrations are squashed into 001_schema.sql, with no Default team.
TERDUT_DEADMAN_* and the env seeding of switches are removed: teams carry
their own. Existing development databases must be recreated.
Security and robustness:
- GET /api/users no longer returns other people's email or ntfy topic to
non-admins.
- The access log records the route pattern, so integration keys and ack
tokens in the path are not written to the log. Server errors are logged.
- Rate limits take the client address TERDUT_TRUSTED_PROXIES hops from the
right of X-Forwarded-For instead of trusting the first, forgeable entry.
- /api/bootstrap runs in a transaction under an advisory lock, so two
concurrent calls cannot both create an administrator.
- API key last_used_at is written at most every five minutes.
Cleanup: remove GET /api/incidents/{id}/alerts, unused exports, SQLite
remnants in comments and config.
Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
67 lines
2.2 KiB
Go
67 lines
2.2 KiB
Go
package models
|
|
|
|
import "time"
|
|
|
|
// Team is the unit of tenancy: it owns its incidents, alerts, schedule and
|
|
// integrations, and a user sees exactly the teams they belong to.
|
|
type Team struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
|
|
// ExternalID identifies a team managed by automation; see handleCreateTeam.
|
|
// Shown to instance service accounts and admins only.
|
|
ExternalID *string `json:"external_id,omitempty"`
|
|
|
|
// Role is the caller's own role in this team, populated when a team is
|
|
// listed for a particular person. Empty when nobody in particular is
|
|
// asking, as in the admin listing.
|
|
Role string `json:"role,omitempty"`
|
|
|
|
// Source says who granted Role, on the endpoint that lists one user's teams:
|
|
// "manual", or "oidc" when the identity provider's groups did.
|
|
Source string `json:"source,omitempty"`
|
|
}
|
|
|
|
// Team roles. An owner configures the team — its schedule, its integrations and
|
|
// who is in it. A member works its incidents.
|
|
const (
|
|
RoleOwner = "owner"
|
|
RoleMember = "member"
|
|
)
|
|
|
|
// TeamMember is one person's membership of one team.
|
|
type TeamMember struct {
|
|
TeamID int64 `json:"team_id"`
|
|
UserID int64 `json:"user_id"`
|
|
Username string `json:"username"`
|
|
Role string `json:"role"`
|
|
JoinedAt time.Time `json:"joined_at"`
|
|
|
|
// Source is who granted the membership: "manual", or "oidc" when the
|
|
// identity provider's groups did and only they can change it.
|
|
Source string `json:"source"`
|
|
}
|
|
|
|
// Integration is how alerts get in, and the only thing that says which team an
|
|
// arriving alert belongs to.
|
|
type Integration struct {
|
|
ID int64 `json:"id"`
|
|
TeamID int64 `json:"team_id"`
|
|
Kind string `json:"kind"`
|
|
Name string `json:"name"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
LastUsedAt *time.Time `json:"last_used_at,omitempty"`
|
|
|
|
// Key is the raw integration key, shown once when the integration is
|
|
// created and never stored. URL is the address to point the sender at,
|
|
// likewise only complete at creation time.
|
|
Key string `json:"key,omitempty"`
|
|
URL string `json:"url,omitempty"`
|
|
}
|
|
|
|
// Integration kinds.
|
|
const (
|
|
IntegrationAlertmanager = "alertmanager"
|
|
)
|