api

package
v0.24.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 14, 2026 License: AGPL-3.0 Imports: 49 Imported by: 0

Documentation

Index

Constants

View Source
const (
	// ApproverHatNone means the viewer may see the item but not resolve it.
	ApproverHatNone = ""
	// ApproverHatAdmin — the admin role, which decides everything.
	ApproverHatAdmin = "admin"
	// ApproverHatDefinition — a member of the grant definition's
	// approver_user_group_uids. Query holds only; it wins over the server chain.
	ApproverHatDefinition = "definition_approver"
	// ApproverHatServer — a member of a group resolved off the target server or
	// its server groups (query approvers for a hold, access approvers for a
	// grant request).
	ApproverHatServer = "server_approver"
)

Approver hats — *why* the current user may resolve something. Returned alongside each pending item so the UI can say which authority the viewer is acting under instead of just enabling a button, which is the difference between a delegated approver trusting the screen and guessing.

View Source
const (
	// Credential failures
	FailureReasonInvalidUsername   = "invalid_username"         // Username not found
	FailureReasonInvalidPassword   = "invalid_password"         // Wrong password
	FailureReasonPasswordChangeReq = "password_change_required" // Initial password not changed

	// Token failures
	FailureReasonTokenInvalid = "token_invalid" // Malformed or unknown token
	FailureReasonTokenExpired = "token_expired" // Token past expiration
	FailureReasonTokenRevoked = "token_revoked" // Token was revoked

	// Account status
	FailureReasonUserDisabled = "user_disabled" // Account disabled by admin
	FailureReasonUserDeleted  = "user_deleted"  // Account was deleted
)

REST API failure reasons

View Source
const AuditEventOAuthRolesSynced = "user.roles_synced"

AuditEventOAuthRolesSynced is written whenever a login changes a user's roles because their directory membership changed. It is the trail an auditor follows to answer "who made this person an admin, and when did they stop being one" once the answer is "the identity provider did".

View Source
const AuditEventUserCreated = "user.created"

AuditEventUserCreated is written when an account comes into existence, whether an admin created it through the users API or a verified OAuth identity auto-provisioned it. Deliberately the same event type for both: "where did this account come from" is one question, and it should not need two queries to answer. The details say which path it took.

Variables

View Source
var (
	// ErrSelfApproval is returned when the requester tries to resolve their
	// own held statement. Four-eyes means four eyes, admin or not.
	ErrSelfApproval = errors.New("you cannot resolve your own query")
	// ErrNotAnApprover is returned when the user is neither an admin nor a
	// member of the grant's approver groups.
	ErrNotAnApprover = errors.New("you are not an approver for this query")
)

Approval authorization errors surfaced outside the HTTP handlers.

View Source
var (
	ErrInvalidUID = errors.New("invalid UID")
)

API errors.

View Source
var ErrRequestOutOfScope = errors.New("grant request is out of the definition's scope")

ErrRequestOutOfScope is returned when a pending request no longer matches its definition's scope — typically because an admin tightened the scope after the request was filed. It hard-blocks the approval rather than silently granting access the current policy no longer allows; an admin who still wants to grant it can always create a direct grant.

Functions

This section is empty.

Types

type AssignGrantRequest added in v0.23.0

type AssignGrantRequest struct {
	// GrantDefinitionID identifies the definition to instantiate — either its
	// uid or its slug, resolved the same way every other definition reference
	// is. A slug always resolves to the live version.
	GrantDefinitionID string    `json:"grant_definition_id" binding:"required"`
	UserID            uuid.UUID `json:"user_id" binding:"required"`
	DatabaseID        uuid.UUID `json:"database_id" binding:"required"`
	// StartsAt defaults to now. The window's *length* is the definition's
	// duration_seconds and is not negotiable here — that is part of the shape.
	StartsAt *time.Time `json:"starts_at"`
}

AssignGrantRequest is the body for POST /grants: an admin issuing a grant directly, without the user having to file a request.

There is deliberately no way to describe the grant's *shape* here. A grant is an instance of a grant definition and nothing else; ad-hoc grants — an admin typing controls and quotas into a form — are what made definitions untrustworthy as the policy source of truth, so the shape comes from the definition or the grant does not exist.

type ChangePasswordRequest

type ChangePasswordRequest struct {
	Username        string `json:"username"`
	CurrentPassword string `json:"current_password" binding:"required"`
	NewPassword     string `json:"new_password" binding:"required"`
}

ChangePasswordRequest represents the request body for authenticated password change Requires re-authentication via username/password (not Bearer token) Username is optional when changing your own password (inferred from :uid param)

type ConnectionInfo added in v0.11.0

type ConnectionInfo struct {
	DatabaseUID  uuid.UUID `json:"database_uid"`
	DatabaseName string    `json:"database_name"`
	Protocol     string    `json:"protocol"`
	Format       string    `json:"format"` // "uri" or "ez-connect"
	URL          string    `json:"url"`
}

ConnectionInfo describes a ready-to-paste connection URL for a single database.

func BuildConnectionURL added in v0.11.0

func BuildConnectionURL(
	db *store.Server,
	user *store.User,
	endpoints store.ResolvedEndpoints,
	apiKey string,
) (ConnectionInfo, bool)

BuildConnectionURL builds a connection URL for the given database, user, and key. When apiKey is "", the placeholder "{DBBAT_KEY}" is substituted in the password slot. Returns (ConnectionInfo{}, false) when the protocol's resolved port is 0.

type ConnectionTestResponse added in v0.18.0

type ConnectionTestResponse struct {
	OK            bool   `json:"ok"`
	Stage         string `json:"stage"`
	Code          string `json:"code"`
	Message       string `json:"message"`
	HostKeyPinned bool   `json:"host_key_pinned,omitempty"`
	KnownHostKey  string `json:"ssh_known_host_key,omitempty"`
	// CAPinned / LearnedCACert are the Kubernetes counterparts: whether this
	// check performed the TOFU pin, and the bundle in force afterwards. Public
	// challenge material, exactly like the host key.
	CAPinned         bool   `json:"k8s_ca_pinned,omitempty"`
	K8sLearnedCACert string `json:"k8s_learned_ca_cert,omitempty"`
	DurationMs       int64  `json:"duration_ms"`
}

ConnectionTestResponse is the API shape of a connectivity check. It mirrors conncheck.Result and carries no secret material — only the stage reached, a machine-readable code, a human-readable message, and the bastion's public host key.

type CreateAPIKeyRequest

type CreateAPIKeyRequest struct {
	Name      string     `json:"name" binding:"required"`
	ExpiresAt *time.Time `json:"expires_at"`
}

CreateAPIKeyRequest represents the request to create an API key

type CreateAPIKeyResponse

type CreateAPIKeyResponse struct {
	ID                   uuid.UUID        `json:"id"`
	Name                 string           `json:"name"`
	Key                  string           `json:"key"` // Only returned once!
	KeyPrefix            string           `json:"key_prefix"`
	ExpiresAt            *time.Time       `json:"expires_at"`
	CreatedAt            time.Time        `json:"created_at"`
	Connections          []ConnectionInfo `json:"connections"`
	ConnectionsTruncated bool             `json:"connections_truncated"`
}

CreateAPIKeyResponse represents the response when creating an API key

type CreateDatabaseRequest

type CreateDatabaseRequest struct {
	Name              string     `json:"name" binding:"required"`
	Description       string     `json:"description"`
	Host              string     `json:"host" binding:"required"`
	Port              int        `json:"port"`
	DatabaseName      string     `json:"database_name"`
	Username          string     `json:"username" binding:"required"`
	Password          string     `json:"password"`
	SSLMode           string     `json:"ssl_mode"`
	Protocol          string     `json:"protocol"`
	OracleServiceName string     `json:"oracle_service_name"`
	MongoAuthSource   string     `json:"mongo_auth_source"`
	Listable          *bool      `json:"listable"`
	ViaUID            *uuid.UUID `json:"via_uid"`
	// SSH bastion secrets (write-only, never returned).
	SSHPrivateKey string `json:"ssh_private_key"`
	SSHPassphrase string `json:"ssh_passphrase"`
	// Kubernetes cluster material. The ServiceAccount bearer token is sent as
	// Password — it is the row's secret, encrypted exactly like a database
	// password — while the CA bundle and the namespace are public and stored
	// in clear. There is deliberately no kubeconfig field: EKS/GKE kubeconfigs
	// authenticate through exec credential plugins, which a server daemon
	// cannot run.
	//
	// K8sCACert is optional: a row that supplies none gets a trust-on-first-use
	// pin instead, learned on the first connect and stored separately. Pasting
	// the bundle remains the stronger setup — see docs/kubernetes.md.
	K8sCACert                string `json:"k8s_ca_cert"`
	K8sNamespace             string `json:"k8s_namespace"`
	K8sInsecureSkipTLSVerify bool   `json:"k8s_insecure_skip_tls_verify"`
	// AccessApproverUserGroupUIDs / QueryApproverUserGroupUIDs name the user
	// groups allowed to decide grant *requests* for this server and to release
	// approval *holds* on statements against it. Omitted or empty falls back to
	// the server groups this server belongs to, and then to admins.
	AccessApproverUserGroupUIDs []uuid.UUID `json:"access_approver_user_group_uids"`
	QueryApproverUserGroupUIDs  []uuid.UUID `json:"query_approver_user_group_uids"`
	// TestConnection asks the API to validate the row by actually dialing it
	// once created. Opt-in, and never fatal: the outcome comes back as a
	// connection_test object alongside the created server.
	TestConnection bool `json:"test_connection"`
}

CreateDatabaseRequest represents the request to create a database (or, when protocol is "ssh", an SSH bastion). Password is optional for SSH rows that authenticate with a private key.

type CreateGrantDefinitionRequest added in v0.10.0

type CreateGrantDefinitionRequest struct {
	Name string `json:"name" binding:"required"`
	// Slug is a stable, human-typeable, machine-friendly identifier for this
	// definition — mandatory at the API level. The server never generates
	// one; that's the frontend's job (derive-from-name until the operator
	// edits it manually), which keeps the API contract explicit for CLI and
	// agent callers.
	Slug                string   `json:"slug" binding:"required"`
	Description         string   `json:"description"`
	DurationSeconds     int64    `json:"duration_seconds" binding:"required"`
	Controls            []string `json:"controls"`
	MaxQueryCounts      *int64   `json:"max_query_counts"`
	MaxBytesTransferred *int64   `json:"max_bytes_transferred"`
	// Priority, when supplied, is stamped verbatim on every grant
	// materialized from this definition instead of the tier its controls
	// would earn. null/omitted — the normal case — leaves it auto.
	Priority *int16 `json:"priority"`
	// AutoApprove, when true, makes grant requests against this definition
	// skip the pending/admin-approval step and materialize the grant
	// instantly.
	AutoApprove bool `json:"auto_approve"`
	// UserGroupUIDs restricts the definition to members of these user groups.
	// Empty/omitted = every user, which is how every pre-scoping definition
	// keeps behaving.
	UserGroupUIDs []uuid.UUID `json:"user_group_uids"`
	// ServerGroupUIDs restricts the definition to the databases currently
	// belonging to these server groups. Empty/omitted = every database.
	ServerGroupUIDs []uuid.UUID `json:"server_group_uids"`
	// ApprovalPatterns are RE2 patterns that suspend a matching statement
	// until a second human approves it. Empty/omitted = no approval gating.
	// Compiled here so a bad pattern is a 400 rather than a runtime surprise
	// on the proxy hot path.
	ApprovalPatterns []string `json:"approval_patterns"`
	// SampleQueries are representative SQL statements saved alongside the
	// patterns to validate them against — a test bench for pattern
	// authoring. See POST /grant-definitions/validate-patterns.
	SampleQueries []string `json:"sample_queries"`
	// ApproverUserGroupUIDs lists the user groups whose members may resolve
	// those holds, in addition to admins. Empty/omitted = admins only.
	ApproverUserGroupUIDs []uuid.UUID `json:"approver_user_group_uids"`

	// RetiredDatabaseUIDs catches the removed per-database scope. It is not a
	// compatibility shim: server groups are a different entity, so there is
	// nothing to fold it onto, and silently dropping a scope restriction on
	// the floor would fail *open*. Its only job is to make the request a 400
	// that names the replacement — see validateDefinitionRequest.
	RetiredDatabaseUIDs []uuid.UUID `json:"database_uids"`

	// RetiredGroupUIDs / RetiredApproverGroupUIDs catch the pre-rename
	// spellings of UserGroupUIDs / ApproverUserGroupUIDs. Server groups made a
	// bare "group" ambiguous, so both fields were renamed; the old spellings
	// are refused rather than folded onto their replacements — silently
	// ignoring a scope restriction fails *open*. See errRetiredGroupUIDs /
	// errRetiredApproverGroupUIDs.
	RetiredGroupUIDs         []uuid.UUID `json:"group_uids"`
	RetiredApproverGroupUIDs []uuid.UUID `json:"approver_group_uids"`
}

CreateGrantDefinitionRequest is the JSON body for POST /grant-definitions.

type CreateGrantRequestRequest added in v0.10.0

type CreateGrantRequestRequest struct {
	// GrantDefinitionID identifies the definition being requested — either
	// its uid or its slug. Widened from uid-only to accept a slug too,
	// rather than adding a sibling field, since this is the one place the
	// API takes a bare definition reference as a request-body value (every
	// other reference is a path param, resolved the same uid-or-slug way).
	GrantDefinitionID string    `json:"grant_definition_id" binding:"required"`
	DatabaseID        uuid.UUID `json:"database_id" binding:"required"`
	Justification     string    `json:"justification"`
}

CreateGrantRequestRequest is the body for POST /grant-requests.

type CreateServerGroupRequest added in v0.24.0

type CreateServerGroupRequest struct {
	Name        string `json:"name" binding:"required"`
	Description string `json:"description"`
	// MemberUIDs, when non-nil, replaces the group's membership. On create it
	// seeds it; on update a nil value leaves membership untouched.
	MemberUIDs []uuid.UUID `json:"member_uids"`
	// AccessApproverUserGroupUIDs / QueryApproverUserGroupUIDs are the
	// group-level fallback for the two approver kinds: they cover every member
	// server that names no approvers of its own. Several groups holding the same
	// server union. Empty means the group names nobody, which leaves the
	// decision to admins.
	//
	// Unlike member_uids these are written on every update, empty included —
	// clearing a list is a real policy change and has to be expressible.
	AccessApproverUserGroupUIDs []uuid.UUID `json:"access_approver_user_group_uids"`
	QueryApproverUserGroupUIDs  []uuid.UUID `json:"query_approver_user_group_uids"`
}

CreateServerGroupRequest is the body for POST /server-groups.

type CreateUserGroupRequest added in v0.18.0

type CreateUserGroupRequest struct {
	Name        string `json:"name" binding:"required"`
	Description string `json:"description"`
	// MemberUIDs, when non-nil, replaces the group's membership. On create
	// it seeds it; on update a nil value leaves membership untouched.
	MemberUIDs []uuid.UUID `json:"member_uids"`
}

CreateUserGroupRequest is the body for POST /user-groups.

type CreateUserRequest

type CreateUserRequest struct {
	Username string   `json:"username" binding:"required"`
	Password string   `json:"password" binding:"required"`
	Roles    []string `json:"roles"`
}

CreateUserRequest represents the request to create a user

type DatabaseLimitedResponse

type DatabaseLimitedResponse struct {
	UID         uuid.UUID `json:"uid"`
	Name        string    `json:"name"`
	Description string    `json:"description"`
}

DatabaseLimitedResponse represents a database with limited info (non-admin)

type DatabaseResponse

type DatabaseResponse struct {
	UID               uuid.UUID  `json:"uid"`
	Name              string     `json:"name"`
	Description       string     `json:"description"`
	Host              string     `json:"host,omitempty"`
	Port              int        `json:"port,omitempty"`
	DatabaseName      string     `json:"database_name,omitempty"`
	Username          string     `json:"username,omitempty"`
	SSLMode           string     `json:"ssl_mode,omitempty"`
	Protocol          string     `json:"protocol,omitempty"`
	OracleServiceName string     `json:"oracle_service_name,omitempty"`
	MongoAuthSource   string     `json:"mongo_auth_source,omitempty"`
	Listable          bool       `json:"listable"`
	CreatedBy         *uuid.UUID `json:"created_by,omitempty"`
	ViaUID            *uuid.UUID `json:"via_uid,omitempty"`
	// SSHKnownHostKey is the TOFU-pinned bastion host key (read-only). Secrets
	// (private key, passphrase) are never returned.
	SSHKnownHostKey string `json:"ssh_known_host_key,omitempty"`
	// Kubernetes cluster material. Public: the CA bundle is challenge material
	// and the namespace is scope, so both round-trip. The ServiceAccount token
	// never does.
	K8sCACert string `json:"k8s_ca_cert,omitempty"`
	// K8sLearnedCACert is the bundle dbbat pinned itself on first connect, when
	// the row supplied none (read-only). Kept apart from K8sCACert so a client
	// can say which of the two is in force: a supplied bundle always wins.
	K8sLearnedCACert         string `json:"k8s_learned_ca_cert,omitempty"`
	K8sNamespace             string `json:"k8s_namespace,omitempty"`
	K8sInsecureSkipTLSVerify bool   `json:"k8s_insecure_skip_tls_verify,omitempty"`
	// The two approver lists. Always rendered, empty included: an empty list is
	// a meaningful state (fall back to the server groups, then to admins), so
	// omitting it would make "cleared" indistinguishable from "unknown".
	AccessApproverUserGroupUIDs []uuid.UUID `json:"access_approver_user_group_uids"`
	QueryApproverUserGroupUIDs  []uuid.UUID `json:"query_approver_user_group_uids"`
	// ConnectionTest is present only when the request set test_connection.
	ConnectionTest *ConnectionTestResponse `json:"connection_test,omitempty"`
}

DatabaseResponse represents a database with full details (admin only)

type DenyGrantRequestRequest added in v0.10.0

type DenyGrantRequestRequest struct {
	Reason string `json:"reason"`
}

DenyGrantRequestRequest is the body for POST /grant-requests/:uid/deny.

type DeviceAuthorizationRequest added in v0.19.0

type DeviceAuthorizationRequest struct {
	ClientName string `json:"client_name"`
	ClientID   string `json:"client_id"`
}

DeviceAuthorizationRequest is the request body for POST /auth/device. client_name is a dbbat extension used for the consent-page label; client_id is accepted for OAuth compatibility but ignored (dbbat is not a multi-client authorization server).

type DeviceAuthorizationResponse added in v0.19.0

type DeviceAuthorizationResponse struct {
	DeviceCode              string `json:"device_code"`
	UserCode                string `json:"user_code"`
	VerificationURI         string `json:"verification_uri"`
	VerificationURIComplete string `json:"verification_uri_complete"`
	ExpiresIn               int    `json:"expires_in"`
	Interval                int    `json:"interval"`
}

DeviceAuthorizationResponse is the RFC 8628 device authorization response.

type DeviceConsentInfo added in v0.19.0

type DeviceConsentInfo struct {
	ClientName string    `json:"client_name"`
	UserCode   string    `json:"user_code"`
	Status     string    `json:"status"`
	ExpiresAt  time.Time `json:"expires_at"`
}

DeviceConsentInfo is the public detail of a device authorization request, safe to show on the (authenticated) consent page.

type DeviceConsentRequest added in v0.19.0

type DeviceConsentRequest struct {
	UserCode string `json:"user_code" binding:"required"`
	Approve  bool   `json:"approve"`
}

DeviceConsentRequest is the request body for POST /auth/device/consent.

type DeviceTokenRequest added in v0.19.0

type DeviceTokenRequest struct {
	GrantType  string `json:"grant_type" binding:"required"`
	DeviceCode string `json:"device_code" binding:"required"`
	ClientID   string `json:"client_id"`
}

DeviceTokenRequest is the request body for POST /auth/device/token.

type DumpMetadata added in v0.23.0

type DumpMetadata struct {
	Available bool  `json:"available"`
	SizeBytes int64 `json:"size_bytes"`
}

DumpMetadata reports whether a session capture is available for download and, if so, how large it is — enough for the UI to label the download action and warn before pulling a multi-megabyte file.

type ErrorBody added in v0.4.0

type ErrorBody struct {
	Code       ErrorCode `json:"code"`
	Message    string    `json:"message"`
	Detail     string    `json:"detail,omitempty"`
	RetryAfter int       `json:"retry_after,omitempty"`
}

ErrorBody is the standard error response structure.

type ErrorCode added in v0.4.0

type ErrorCode string

ErrorCode is a machine-readable error code returned in API responses.

const (
	// ErrCodeInternalError indicates an unexpected server error.
	ErrCodeInternalError ErrorCode = "INTERNAL_ERROR"
	// ErrCodeValidationError indicates invalid input.
	ErrCodeValidationError ErrorCode = "VALIDATION_ERROR"
	// ErrCodeNotFound indicates the requested resource was not found.
	ErrCodeNotFound ErrorCode = "NOT_FOUND"
	// ErrCodeUnauthorized indicates authentication is required.
	ErrCodeUnauthorized ErrorCode = "UNAUTHORIZED"
	// ErrCodeForbidden indicates insufficient permissions.
	ErrCodeForbidden ErrorCode = "FORBIDDEN"
	// ErrCodeInvalidCredentials indicates wrong username or password.
	ErrCodeInvalidCredentials ErrorCode = "INVALID_CREDENTIALS"
	// ErrCodePasswordChangeRequired indicates the user must change their password.
	ErrCodePasswordChangeRequired ErrorCode = "PASSWORD_CHANGE_REQUIRED"
	// ErrCodeWeakPassword indicates the password does not meet requirements.
	ErrCodeWeakPassword ErrorCode = "WEAK_PASSWORD"
	// ErrCodeRateLimited indicates too many requests.
	ErrCodeRateLimited ErrorCode = "RATE_LIMITED"
	// ErrCodeConflict indicates a state conflict (e.g. trying to transition
	// a non-pending grant request, or duplicating a unique resource).
	ErrCodeConflict ErrorCode = "CONFLICT"
	// ErrCodeOAuthFailed indicates an OAuth authentication failure.
	ErrCodeOAuthFailed ErrorCode = "OAUTH_FAILED"
	// ErrCodeOAuthStateMismatch indicates an invalid or expired OAuth state.
	ErrCodeOAuthStateMismatch ErrorCode = "OAUTH_STATE_MISMATCH"
	// ErrCodeOAuthProviderError indicates the OAuth provider returned an error.
	ErrCodeOAuthProviderError ErrorCode = "OAUTH_PROVIDER_ERROR"
	// ErrCodeOAuthUserNotLinked indicates no account is linked to the OAuth identity.
	ErrCodeOAuthUserNotLinked ErrorCode = "OAUTH_USER_NOT_LINKED"
	// ErrCodeOAuthExchangeInvalid indicates the one-time login exchange code is
	// unknown, already redeemed or expired.
	ErrCodeOAuthExchangeInvalid ErrorCode = "OAUTH_EXCHANGE_INVALID"
	// ErrCodeOAuthWrongWorkspace indicates the wrong OAuth workspace was used.
	ErrCodeOAuthWrongWorkspace ErrorCode = "OAUTH_WRONG_WORKSPACE"
	// ErrCodeDuplicateName indicates a resource with that name already exists.
	ErrCodeDuplicateName ErrorCode = "DUPLICATE_NAME"
	// ErrCodeTargetMatchesSelf indicates the target matches the storage database.
	ErrCodeTargetMatchesSelf ErrorCode = "TARGET_MATCHES_SELF"
	// ErrCodeGrantExpired indicates the access grant has expired.
	ErrCodeGrantExpired ErrorCode = "GRANT_EXPIRED"
	// ErrCodeQuotaExceeded indicates a usage quota was exceeded.
	ErrCodeQuotaExceeded ErrorCode = "QUOTA_EXCEEDED"
)

type GrantSummary added in v0.23.0

type GrantSummary struct {
	UID                     uuid.UUID `json:"uid"`
	Controls                []string  `json:"controls"`
	StartsAt                time.Time `json:"starts_at"`
	ExpiresAt               time.Time `json:"expires_at"`
	Revoked                 bool      `json:"revoked"`
	Priority                int16     `json:"priority"`
	GrantDefinitionID       uuid.UUID `json:"grant_definition_id"`
	GrantDefinitionName     string    `json:"grant_definition_name"`
	GrantDefinitionSlug     string    `json:"grant_definition_slug"`
	GrantDefinitionIsActive bool      `json:"grant_definition_is_active"`
}

GrantSummary is the slice of an access grant a connection detail page needs to answer "under which grant did this session run?" without a second round trip: controls, validity window, revocation state and priority.

Controls are read from the grant's definition — grants carry no shape of their own — so the summary also names that definition, which is what the page links to for the full policy.

type LoginExchangeRequest added in v0.23.0

type LoginExchangeRequest struct {
	Code string `json:"code" binding:"required"`
}

LoginExchangeRequest is the request body for POST /auth/oauth/exchange.

type LoginExchangeResponse added in v0.23.0

type LoginExchangeResponse struct {
	AccessToken string `json:"access_token"`
	TokenType   string `json:"token_type"`
}

LoginExchangeResponse hands the web session token back to the SPA. Shaped like the device-grant token response so both login paths look the same to a client.

type LoginRequest

type LoginRequest struct {
	Username string `json:"username" binding:"required"`
	Password string `json:"password" binding:"required"`
}

LoginRequest represents the request body for login

type LoginResponse

type LoginResponse struct {
	Token     string       `json:"token"`
	ExpiresAt string       `json:"expires_at"`
	User      UserResponse `json:"user"`
}

LoginResponse represents the response for a successful login

type MeResponse

type MeResponse struct {
	UID                    string          `json:"uid"`
	Username               string          `json:"username"`
	Roles                  []string        `json:"roles"`
	PasswordChangeRequired bool            `json:"password_change_required"`
	Session                SessionResponse `json:"session"`
}

MeResponse represents the response for /auth/me

type PatternValidationResult added in v0.23.0

type PatternValidationResult struct {
	Pattern string `json:"pattern"`
	// Error is the compile error message, present only when the pattern
	// failed to compile. This is exactly the error NewApprovalGate silently
	// swallows (with only a server-side warning log) when building the live
	// per-session gate — see approval.go — surfaced here instead of staying
	// invisible until a live statement fails to hold.
	Error string `json:"error,omitempty"`
}

PatternValidationResult reports whether one pattern (by its position in the request's patterns array) compiled.

type PreLoginPasswordChangeRequest

type PreLoginPasswordChangeRequest struct {
	Username        string `json:"username" binding:"required"`
	CurrentPassword string `json:"current_password" binding:"required"`
	NewPassword     string `json:"new_password" binding:"required"`
}

PreLoginPasswordChangeRequest represents the request body for pre-login password change

type QueryValidationResult added in v0.23.0

type QueryValidationResult struct {
	Query      string `json:"query"`
	Normalized string `json:"normalized"`
	Matched    bool   `json:"matched"`
	// MatchedPattern is the source text of the first pattern that matched,
	// present only when Matched is true.
	MatchedPattern string `json:"matched_pattern,omitempty"`
}

QueryValidationResult reports how one sample query fares against the patterns: the NormalizeSQL form the patterns actually run against, and which pattern matched it first, if any — same semantics as ApprovalGate.Match on the proxy hot path.

type RateLimiter

type RateLimiter struct {
	// contains filtered or unexported fields
}

RateLimiter implements a sliding window rate limiter

func NewRateLimiter

func NewRateLimiter(cfg config.RateLimitConfig) *RateLimiter

NewRateLimiter creates a new rate limiter with the given configuration

func (*RateLimiter) GetStats

func (rl *RateLimiter) GetStats(userID *uuid.UUID, ip string) (int, time.Time)

GetStats returns statistics for a given key (for testing/debugging)

func (*RateLimiter) Middleware

func (rl *RateLimiter) Middleware() gin.HandlerFunc

Middleware returns a Gin middleware for rate limiting

func (*RateLimiter) PostAuthMiddleware

func (rl *RateLimiter) PostAuthMiddleware() gin.HandlerFunc

PostAuthMiddleware is a rate limiter middleware that runs after authentication It uses the authenticated user ID for rate limiting

func (*RateLimiter) PreAuthMiddleware

func (rl *RateLimiter) PreAuthMiddleware() gin.HandlerFunc

PreAuthMiddleware is a rate limiter middleware that runs before authentication It rate limits by IP for unauthenticated requests

type ResetPasswordRequest added in v0.3.0

type ResetPasswordRequest struct {
	NewPassword string `json:"new_password" binding:"required"`
}

ResetPasswordRequest represents the request body for admin password reset

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server represents the REST API server.

func NewServer

func NewServer(dataStore *store.Store, encryptionKey []byte, logger *slog.Logger, cfg *config.Config) *Server

NewServer creates a new API server.

func (*Server) Notifier added in v0.20.0

func (s *Server) Notifier() *notify.SlackNotifier

Notifier exposes the server's Slack client so the process wiring can build the approval escalator on top of it instead of opening a second connection. nil when Slack notifications are disabled.

func (*Server) ResolveQueryApprovalAs added in v0.20.0

func (s *Server) ResolveQueryApprovalAs(ctx context.Context, user *store.User, queryUID uuid.UUID, status, reason string) error

ResolveQueryApprovalAs authorizes and applies a decision for a user that did not come through the HTTP handlers (today: a Slack button click). It repeats every check the REST path makes — pending state, self-approval, approver membership — because "it came from Slack" is not an authorization.

func (*Server) SetDumpStorage added in v0.23.0

func (s *Server) SetDumpStorage(uploader *dump.Uploader)

SetDumpStorage installs the blob store holding uploaded session captures, so downloads can fall back to it when the capture is no longer (or never was) in this replica's local spool. Called by the process wiring; nil is fine and means local-only captures.

func (*Server) SetEventPlumbing added in v0.20.0

func (s *Server) SetEventPlumbing(broker *events.Broker, registry *approval.Registry, notifier approvalEscalator)

SetEventPlumbing installs the shared broker and approval registry. Called by the process wiring so the API and the proxies publish into — and resolve against — the same instances.

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the server.

func (*Server) Start

func (s *Server) Start(addr string) error

Start starts the API server.

func (*Server) StartEventListener added in v0.20.0

func (s *Server) StartEventListener(ctx context.Context)

StartEventListener subscribes to the cross-replica notification channels and republishes what arrives into the local broker, so an admin whose WebSocket landed on replica B still sees a query held on replica A.

The payload carries only ids; the row is re-read here. That keeps SQL text out of the database's notification queue and means a late subscriber gets the current state rather than a stale snapshot.

type SessionResponse

type SessionResponse struct {
	ExpiresAt string `json:"expires_at"`
	CreatedAt string `json:"created_at"`
}

SessionResponse represents session info in me response

type UpdateDatabaseRequest

type UpdateDatabaseRequest struct {
	Description       *string    `json:"description"`
	Host              *string    `json:"host"`
	Port              *int       `json:"port"`
	DatabaseName      *string    `json:"database_name"`
	Username          *string    `json:"username"`
	Password          *string    `json:"password"`
	SSLMode           *string    `json:"ssl_mode"`
	Protocol          *string    `json:"protocol"`
	OracleServiceName *string    `json:"oracle_service_name"`
	MongoAuthSource   *string    `json:"mongo_auth_source"`
	Listable          *bool      `json:"listable"`
	ViaUID            *uuid.UUID `json:"via_uid"`
	// ClearViaUID, when true, removes the SSH tunnel (direct dial). Distinct
	// from an omitted via_uid, which leaves the tunnel unchanged.
	ClearViaUID bool `json:"clear_via_uid"`
	// SSH bastion secrets (write-only, never returned).
	SSHPrivateKey *string `json:"ssh_private_key"`
	SSHPassphrase *string `json:"ssh_passphrase"`
	// Kubernetes cluster material; see CreateDatabaseRequest. The bearer token
	// is rotated through Password like any other secret.
	K8sCACert                *string `json:"k8s_ca_cert"`
	K8sNamespace             *string `json:"k8s_namespace"`
	K8sInsecureSkipTLSVerify *bool   `json:"k8s_insecure_skip_tls_verify"`
	// K8sResetLearnedCACert forgets the TOFU-learned bundle so the next connect
	// pins afresh. It is the exit from a stale pin when the cluster's CA
	// rotated and you do not have the new bundle to paste; supplying a
	// non-empty k8s_ca_cert clears it too, since a supplied bundle supersedes
	// anything learned.
	K8sResetLearnedCACert bool `json:"k8s_reset_learned_ca_cert"`
	// AccessApproverUserGroupUIDs / QueryApproverUserGroupUIDs replace the
	// server's approver lists wholesale. Omitted (null) leaves them alone; an
	// explicit `[]` clears them, handing the decision back to the server groups
	// and then to admins. Effective immediately, for grant requests already
	// filed and statements already parked — see docs/approvals.md.
	AccessApproverUserGroupUIDs *[]uuid.UUID `json:"access_approver_user_group_uids"`
	QueryApproverUserGroupUIDs  *[]uuid.UUID `json:"query_approver_user_group_uids"`
	// TestConnection asks the API to validate the row by actually dialing it
	// once updated. Opt-in, and never fatal.
	TestConnection bool `json:"test_connection"`
}

UpdateDatabaseRequest represents the request to update a database

type UpdateGrantDefinitionRequest added in v0.10.0

type UpdateGrantDefinitionRequest struct {
	Name                     *string     `json:"name"`
	Slug                     *string     `json:"slug"`
	Description              *string     `json:"description"`
	DurationSeconds          *int64      `json:"duration_seconds"`
	Controls                 []string    `json:"controls"`
	MaxQueryCounts           *int64      `json:"max_query_counts"`
	ClearMaxQueryCounts      bool        `json:"clear_max_query_counts"`
	MaxBytesTransferred      *int64      `json:"max_bytes_transferred"`
	ClearMaxBytesTransferred bool        `json:"clear_max_bytes_transferred"`
	Priority                 *int16      `json:"priority"`
	ClearPriority            bool        `json:"clear_priority"`
	AutoApprove              *bool       `json:"auto_approve"`
	UserGroupUIDs            []uuid.UUID `json:"user_group_uids"`
	ServerGroupUIDs          []uuid.UUID `json:"server_group_uids"`
	ApprovalPatterns         []string    `json:"approval_patterns"`
	SampleQueries            []string    `json:"sample_queries"`
	ApproverUserGroupUIDs    []uuid.UUID `json:"approver_user_group_uids"`

	// RetiredDatabaseUIDs is refused rather than folded; see
	// CreateGrantDefinitionRequest.RetiredDatabaseUIDs.
	RetiredDatabaseUIDs []uuid.UUID `json:"database_uids"`

	// RetiredGroupUIDs / RetiredApproverGroupUIDs are refused rather than
	// folded; see CreateGrantDefinitionRequest.RetiredGroupUIDs.
	RetiredGroupUIDs         []uuid.UUID `json:"group_uids"`
	RetiredApproverGroupUIDs []uuid.UUID `json:"approver_group_uids"`
}

UpdateGrantDefinitionRequest is the JSON body for PATCH /grant-definitions/:uid. Unlike CreateGrantDefinitionRequest, every field is optional and a field absent from the body is left untouched on the existing definition rather than reset to its zero value. This is what makes a targeted PATCH — like the auto-approve toggle, which only means to flip one field — safe: it used to reuse CreateGrantDefinitionRequest as a full-replace body, so any field the caller didn't round-trip (most notably approval_patterns and approver_user_group_uids) got silently wiped.

Slice fields (controls, user_group_uids, server_group_uids, approval_patterns, approver_user_group_uids) distinguish "absent" from "explicitly cleared": encoding/json leaves a slice field nil when its key is missing (or its value is JSON null), but allocates a non-nil empty slice for a present `[]`. Only a non-nil slice is applied, so an admin can still empty one of these lists via PATCH without sending anything else.

max_query_counts, max_bytes_transferred and priority are themselves nullable in the domain (null means "no limit" / "auto"), so a plain pointer can't tell "absent" from "explicit null" — both decode to a nil pointer. Each gets a companion Clear* flag instead, mirroring UpdateDatabaseRequest.ClearViaUID.

type UpdateServerGroupRequest added in v0.24.0

type UpdateServerGroupRequest = CreateServerGroupRequest

UpdateServerGroupRequest is the body for PATCH /server-groups/:uid. Same shape as create — this surface is too small to warrant a separate partial type, exactly like the user-group one.

type UpdateUserGroupRequest added in v0.18.0

type UpdateUserGroupRequest = CreateUserGroupRequest

UpdateUserGroupRequest is the body for PATCH /user-groups/:uid. Same shape as create — this surface is too small to warrant a separate partial type.

type UpdateUserRequest

type UpdateUserRequest struct {
	Password *string  `json:"password"`
	Roles    []string `json:"roles"`
	// UserGroupUIDs, when non-nil, replaces the user's user-group memberships
	// wholesale. Admin-only, like Roles.
	UserGroupUIDs []uuid.UUID `json:"user_group_uids"`
	// RetiredGroupUIDs catches the pre-rename spelling of UserGroupUIDs. It
	// is refused rather than folded onto UserGroupUIDs — silently ignoring a
	// scope restriction fails *open*. See errRetiredGroupUIDs.
	RetiredGroupUIDs []uuid.UUID `json:"group_uids"`
}

UpdateUserRequest represents the request to update a user

type UserResponse

type UserResponse struct {
	UID                    string   `json:"uid"`
	Username               string   `json:"username"`
	Roles                  []string `json:"roles"`
	PasswordChangeRequired bool     `json:"password_change_required"`
}

UserResponse represents user info in login/me responses

type ValidatePatternsRequest added in v0.23.0

type ValidatePatternsRequest struct {
	// Patterns are the RE2 approval-pattern sources to test — typically a
	// grant definition's approval_patterns field, saved or still being
	// edited in the dialog.
	Patterns []string `json:"patterns"`
	// Queries are sample SQL statements to test the patterns against —
	// typically a definition's sample_queries field.
	Queries []string `json:"queries"`
}

ValidatePatternsRequest is the JSON body for POST /grant-definitions/validate-patterns.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL