// Package api defines the JSON types of Jarl's HTTP API, shared by the // server and the Go client used by the CLI. package api import ( "encoding/json" "time" "jarl/internal/model" "jarl/internal/nscheck" ) // Version is the API path prefix. const Prefix = "/api/v1" // Zone is a zone as returned by the API. type Zone struct { model.Zone Display string `json:"display_name"` View string `json:"view,omitempty"` Records int `json:"record_count"` ZoneFile string `json:"zone_file,omitempty"` // the file named loads RName string `json:"rname_email,omitempty"` // Pending is set when listing with a changeset: add, change or delete. Pending string `json:"pending,omitempty"` } // Record is a record as returned by the API. type Record struct { model.Record Zone string `json:"zone"` View string `json:"view,omitempty"` RelativeName string `json:"relative_name"` EffectiveTTL uint32 `json:"effective_ttl"` // Text is the decoded value of TXT and SPF records. Text *string `json:"text,omitempty"` // Pending is set when listing with a changeset: add, change or delete. Pending string `json:"pending,omitempty"` } // View is a view with its zone count. type View struct { model.View Zones int `json:"zone_count"` Color string `json:"color,omitempty"` // web UI colour, see model.ViewColors } // ViewColor sets the colour the web UI shows a view in. type ViewColor struct { Color string `json:"color"` } // ACL is an ACL with the objects that use it. type ACL struct { model.ACL UsedBy []string `json:"used_by"` } // TSIGKey is a key; Secret is only filled when asked for and allowed. type TSIGKey struct { model.TSIGKey UsedBy []string `json:"used_by"` } // Option is an option with display helpers. type Option struct { model.Option Where string `json:"where"` // e.g. "view internal" Description string `json:"description,omitempty"` Status string `json:"status,omitempty"` } // User is a user with formatted grants. type User struct { model.User GrantStrings []string `json:"grant_strings"` } // Token is an API token. Secret is only set when the token is created. type Token struct { model.Token GrantStrings []string `json:"grant_strings,omitempty"` Secret string `json:"token,omitempty"` } // Whoami describes the authenticated principal. type Whoami struct { User User `json:"user"` Method string `json:"method"` Token *Token `json:"token,omitempty"` CSRFToken string `json:"csrf_token,omitempty"` Permissions []string `json:"permissions"` Admin bool `json:"admin"` Version string `json:"version"` } // LoginRequest logs in. type LoginRequest struct { Username string `json:"username"` Password string `json:"password"` } // ErrorBody is the error response. type ErrorBody struct { Error ErrorInfo `json:"error"` } // ErrorInfo describes an error. type ErrorInfo struct { Code string `json:"code"` Message string `json:"message"` Op int `json:"op,omitempty"` Problems json.RawMessage `json:"problems,omitempty"` Check json.RawMessage `json:"check,omitempty"` Result json.RawMessage `json:"result,omitempty"` } // ChangeRequest is a request in a batch (POST /apply or changeset ops). type ChangeRequest struct { Op string `json:"op"` ID int64 `json:"id,omitempty"` BaseVersion int `json:"base_version,omitempty"` Data json.RawMessage `json:"data,omitempty"` } // BatchRequest applies (or stages) several requests at once. type BatchRequest struct { Description string `json:"description,omitempty"` Ops []ChangeRequest `json:"ops"` DryRun bool `json:"dry_run,omitempty"` Force bool `json:"force,omitempty"` // Apply (for POST /changesets) applies the new changeset at once. Apply bool `json:"apply,omitempty"` } // MutationResponse is returned by endpoints that change the configuration. type MutationResponse struct { Applied bool `json:"applied"` Staged bool `json:"staged,omitempty"` DryRun bool `json:"dry_run,omitempty"` ChangesetID int64 `json:"changeset_id,omitempty"` Result json.RawMessage `json:"result,omitempty"` Changeset json.RawMessage `json:"changeset,omitempty"` Added []model.Op `json:"added,omitempty"` Warnings []string `json:"warnings,omitempty"` Problems json.RawMessage `json:"problems,omitempty"` Object json.RawMessage `json:"object,omitempty"` } // Changeset is a changeset as returned by the API. type Changeset struct { model.Changeset Undoable bool `json:"undoable"` } // StatusResponse is GET /status. type StatusResponse struct { Version string `json:"version"` Engine json.RawMessage `json:"engine"` Time time.Time `json:"time"` // ExpiringTokens are tokens that expire within TokenExpiryWarning or // expired less than that long ago (not revoked): every user's for // administrators, otherwise the caller's own. ExpiringTokens []Token `json:"expiring_tokens"` // Backup is the state of database backups (administrators only). Backup *BackupStatus `json:"backup,omitempty"` } // TokenExpiresHeader is sent with responses to a token that expires // soon (model.TokenExpiryWarning), holding its expiry time (RFC 3339). The // CLI warns its user when it sees it. const TokenExpiresHeader = "Jarl-Token-Expires" // BackupStatus describes the newest database backup, as recorded by // 'jarld backup'. type BackupStatus struct { LastAt *time.Time `json:"last_at,omitempty"` LastFile string `json:"last_file,omitempty"` LastSize int64 `json:"last_size,omitempty"` LastError string `json:"last_error,omitempty"` // LastErrorAt is when the newest failed backup ran (only reported // when it is newer than the last success). LastErrorAt *time.Time `json:"last_error_at,omitempty"` Dir string `json:"dir"` // WarnAfter (seconds) is how old the newest backup may get before // Stale is set (0: never). WarnAfter int64 `json:"warn_after"` Stale bool `json:"stale"` } // SearchResult is one search hit. type SearchResult struct { Kind string `json:"kind"` // zone, record, acl, view, key ID int64 `json:"id"` ZoneID int64 `json:"zone_id,omitempty"` Label string `json:"label"` Detail string `json:"detail,omitempty"` } // PasswordChange changes a password. type PasswordChange struct { Current string `json:"current_password,omitempty"` Password string `json:"password"` } // UserInput creates or updates a user. type UserInput struct { Username *string `json:"username,omitempty"` Password *string `json:"password,omitempty"` Admin *bool `json:"admin,omitempty"` Grants *[]model.Grant `json:"grants,omitempty"` // GrantStrings are grants in compact form ("records:write zones=x"). GrantStrings *[]string `json:"grant_strings,omitempty"` Role *string `json:"role,omitempty"` // a role template name Disabled *bool `json:"disabled,omitempty"` Comment *string `json:"comment,omitempty"` } // TokenInput creates a token. type TokenInput struct { Name string `json:"name"` Grants *[]model.Grant `json:"grants,omitempty"` GrantStrings *[]string `json:"grant_strings,omitempty"` Role string `json:"role,omitempty"` ExpiresIn string `json:"expires_in,omitempty"` // e.g. "90d", "720h" ExpiresAt *time.Time `json:"expires_at,omitempty"` } // TokenRotation gives a token a new secret (POST /tokens/{id}/rotate). // Every field is optional. type TokenRotation struct { // Grace keeps the old secret working this long (e.g. "1h", "2d"; at // most 7d), so clients can be updated without an outage. Default: // the old secret stops at once. Grace string `json:"grace,omitempty"` // The new expiry. Default: a token that expires gets its lifetime // again, counted from now; one that doesn't, still doesn't. ExpiresIn string `json:"expires_in,omitempty"` ExpiresAt *time.Time `json:"expires_at,omitempty"` NoExpiry bool `json:"no_expiry,omitempty"` } // ZoneImport imports zone file text into a zone. type ZoneImport struct { Text string `json:"text"` // Mode "merge" adds missing records; "replace" makes the zone equal // to the file (deleting other records). Mode string `json:"mode"` DryRun bool `json:"dry_run,omitempty"` // UseSOA also takes the SOA timers and default TTL from the file. UseSOA bool `json:"use_soa,omitempty"` } // SerialsResponse is GET /zones/{zone}/serials. type SerialsResponse struct { Zone string `json:"zone"` View string `json:"view,omitempty"` Type string `json:"type"` // Serial is what this server serves: named's loaded serial, or for a // primary zone Jarl's when named can't be asked (SerialSource says // which). Serial *uint32 `json:"serial"` SerialSource string `json:"serial_source,omitempty"` Servers []nscheck.Result `json:"servers"` Notes []string `json:"notes"` CheckedAt time.Time `json:"checked_at"` } // DelegationResponse is GET /zones/{zone}/delegation. type DelegationResponse struct { *nscheck.DelegationReport View string `json:"view,omitempty"` Notes []string `json:"notes"` CheckedAt time.Time `json:"checked_at"` }