diff --git a/server/cmd/main.go b/server/cmd/main.go index 818cf8b..8d5a573 100644 --- a/server/cmd/main.go +++ b/server/cmd/main.go @@ -29,6 +29,24 @@ import ( "github.com/gin-gonic/gin" ) +// @title Vantage API +// @version 1.0 +// @description The Vantage control plane REST API. Authenticate with a browser session cookie, or with an API token created under Settings → API tokens. +// @BasePath /api +// +// @securityDefinitions.apikey cookieAuth +// @in cookie +// @name km_session +// +// @securityDefinitions.apikey bearerAuth +// @in header +// @name Authorization +// @description An API token, sent as "Bearer vt_…". Scoped and optionally expiring. +// +// @securityDefinitions.apikey esoAuth +// @in header +// @name Authorization +// @description The External Secrets read token, rotated under Settings. It reaches /api/secrets/{group}/values and nothing else. It is a different credential from an API token, and the two must never be substituted for one another. func main() { mongoURI := getEnv("MONGO_URI", "mongodb://localhost:27017") diff --git a/server/internal/api/types.go b/server/internal/api/types.go new file mode 100644 index 0000000..4fe2314 --- /dev/null +++ b/server/internal/api/types.go @@ -0,0 +1,239 @@ +package api + +import ( + "time" + + "gitea.hostxtra.co.uk/mrhid6/vantage/server/internal/models" + "gitea.hostxtra.co.uk/mrhid6/vantage/shared/license" +) + +// ErrorResponse is the shape every failing endpoint answers with. Some also +// carry a machine-readable code; it is omitted when absent rather than empty. +type ErrorResponse struct { + Error string `json:"error"` + Code string `json:"code,omitempty"` +} + +// LimitExceededResponse is what a create route answers when a licence cap +// would be exceeded. +type LimitExceededResponse struct { + Error string `json:"error"` + Limit string `json:"limit"` + Current int `json:"current"` + Max int `json:"max"` +} + +// LicenceErrorResponse pairs an error with a machine-readable reason rather +// than a code — used only on the two licence rejection paths that predate the +// error/code convention used everywhere else. +type LicenceErrorResponse struct { + Error string `json:"error"` + Reason string `json:"reason,omitempty"` +} + +// Small, reused acknowledgement shapes. Several unrelated handlers happen to +// answer with exactly one of these. +type DeletedResponse struct { + Deleted bool `json:"deleted"` +} + +type RevokedResponse struct { + Revoked bool `json:"revoked"` +} + +type SavedResponse struct { + Saved bool `json:"saved"` +} + +type AcknowledgedResponse struct { + Acknowledged bool `json:"acknowledged"` +} + +type OKResponse struct { + OK bool `json:"ok"` +} + +type CancelledResponse struct { + Cancelled bool `json:"cancelled"` +} + +type UpdatedResponse struct { + Updated bool `json:"updated"` +} + +type MessageResponse struct { + Message string `json:"message"` +} + +type StatusResponse struct { + Status string `json:"status"` +} + +// --- servers / keys --- + +type TagsResponse struct { + Tags map[string]string `json:"tags"` +} + +type CreateServerResponse struct { + Server *models.Server `json:"server"` + Token string `json:"token"` + ServerID string `json:"server_id"` +} + +type NewServerResponse struct { + ServerID string `json:"server_id"` + PreRegToken string `json:"pre_reg_token"` + InstallCommand string `json:"install_command"` + InstallCommandPS string `json:"install_command_ps"` +} + +// ServerDetailResponse is a server with its resolved key assignments. +type ServerDetailResponse struct { + *models.Server + Keys interface{} `json:"keys"` +} + +type GenerateKeyResponse struct { + Message string `json:"message"` + CommandID string `json:"command_id"` + ServerID string `json:"server_id"` +} + +type PrivateKeyResponse struct { + PrivateKey string `json:"private_key"` +} + +// KeyDetailResponse is a key with its resolved server assignments. +type KeyDetailResponse struct { + *models.Key + Assignments any `json:"assignments"` +} + +type AgentVersionResponse struct { + Version string `json:"version"` +} + +type UpdateAgentResponse struct { + Message string `json:"message"` + Version string `json:"version"` +} + +type AuditEventsResponse struct { + Events []models.AuditEvent `json:"events"` + Total int64 `json:"total"` +} + +// --- tokens --- + +type ListTokensResponse struct { + Tokens []models.APIToken `json:"tokens"` + All bool `json:"all"` +} + +type TokenScopesResponse struct { + Scopes []string `json:"scopes"` +} + +type CreateTokenRequest struct { + Name string `json:"name"` + Role string `json:"role"` + Scopes []string `json:"scopes"` + ExpiresInDays *int `json:"expires_in_days,omitempty"` +} + +type CreateTokenResponse struct { + // Token is the plaintext, returned exactly once and stored nowhere. + Token string `json:"token"` + Record models.APIToken `json:"record"` +} + +// --- secrets --- + +type GroupResponse struct { + Group string `json:"group"` +} + +type SecretGroupResponse struct { + Group string `json:"group"` + Secrets []models.Secret `json:"secrets"` +} + +type RevealSecretResponse struct { + Value string `json:"value"` +} + +type SecretsTokenResponse struct { + Token string `json:"token"` +} + +// --- auth providers --- + +type TestProviderResponse struct { + OK bool `json:"ok"` + Message string `json:"message"` +} + +// --- licence --- + +type LicencePostResponse struct { + State license.State `json:"state"` + Tier string `json:"tier"` + ExpiresAt *time.Time `json:"expires_at"` +} + +// --- vulnerabilities --- + +// VulnSummaryResponse's four DB-freshness fields are only present at all when +// a vulndb_meta document exists; LastError is separately omitted from that +// group when empty, matching the handler's original conditional gin.H. +type VulnSummaryResponse struct { + Counts map[string]int `json:"counts"` + DBVersion *int `json:"db_version,omitempty"` + PulledAt *time.Time `json:"pulled_at,omitempty"` + LastFullScanAt *time.Time `json:"last_full_scan_at,omitempty"` + LastError string `json:"last_error,omitempty"` +} + +type QueuedResponse struct { + Queued int64 `json:"queued"` +} + +type ReportedResponse struct { + Reported bool `json:"reported"` +} + +// --- workflows --- + +type SeedDefaultsResponse struct { + Created int `json:"created"` + Updated int `json:"updated"` +} + +type RunWorkflowResponse struct { + RunID string `json:"run_id"` +} + +type ScheduleResponse struct { + Schedule models.Schedule `json:"schedule"` + NextRunAt *time.Time `json:"next_run_at"` +} + +type OccurrencesResponse struct { + Occurrences []time.Time `json:"occurrences"` +} + +// --- console --- + +type ConsoleConnectResponse struct { + SessionID string `json:"session_id"` + Token string `json:"token"` + WSPath string `json:"ws_path"` +} + +// --- workloads --- + +type WorkloadLogsResponse struct { + Text string `json:"text"` + Truncated bool `json:"truncated"` +}