Documentation
¶
Overview ¶
Package store provides database access and persistence for DBBat.
Index ¶
- Constants
- Variables
- func ExtractSourceIP(addr net.Addr) string
- func IsMySQLFamily(protocol string) bool
- type APIKey
- type APIKeyFilter
- type AccessGrant
- type AuditEvent
- type AuditFilter
- type AuditLog
- type Connection
- type ConnectionFilter
- type DSNComponents
- type Database
- type DatabaseUpdate
- type Grant
- type GrantDefinition
- type GrantDefinitionFilter
- type GrantFilter
- type GrantRequest
- type GrantRequestFilter
- type GrantRequestStatus
- type MigrationInfo
- type OAuthState
- type Options
- type Query
- type QueryFilter
- type QueryParameters
- type QueryRow
- type QueryRowModel
- type QueryRowsCursor
- type QueryRowsResult
- type QueryWithRows
- type Store
- func (s *Store) ApproveGrantRequest(ctx context.Context, uid, decidedBy uuid.UUID) (*Grant, *GrantRequest, error)
- func (s *Store) CancelGrantRequest(ctx context.Context, uid, byUser uuid.UUID) (*GrantRequest, error)
- func (s *Store) CleanupExpiredOAuthStates(ctx context.Context) (int64, error)
- func (s *Store) Close()
- func (s *Store) CloseConnection(ctx context.Context, uid uuid.UUID) error
- func (s *Store) ConsumeOAuthState(ctx context.Context, stateToken string) (*OAuthState, error)
- func (s *Store) CreateAPIKey(ctx context.Context, userID uuid.UUID, name string, expiresAt *time.Time, ...) (*APIKey, string, error)
- func (s *Store) CreateAPIKeyWithValue(ctx context.Context, userID uuid.UUID, name string, plainKey string, ...) (*APIKey, error)
- func (s *Store) CreateConnection(ctx context.Context, userID, databaseID uuid.UUID, sourceIP string) (*Connection, error)
- func (s *Store) CreateDatabase(ctx context.Context, db *Database, encryptionKey []byte) (*Database, error)
- func (s *Store) CreateGrant(ctx context.Context, grant *Grant) (*Grant, error)
- func (s *Store) CreateGrantDefinition(ctx context.Context, def *GrantDefinition) (*GrantDefinition, error)
- func (s *Store) CreateGrantRequest(ctx context.Context, req *GrantRequest) (*GrantRequest, error)
- func (s *Store) CreateOAuthState(ctx context.Context, state *OAuthState) (*OAuthState, error)
- func (s *Store) CreateQuery(ctx context.Context, query *Query) (*Query, error)
- func (s *Store) CreateUser(ctx context.Context, username, passwordHash string, roles []string) (*User, error)
- func (s *Store) CreateUserIdentity(ctx context.Context, identity *UserIdentity) (*UserIdentity, error)
- func (s *Store) CreateWebSession(ctx context.Context, userID uuid.UUID) (*APIKey, string, error)
- func (s *Store) DB() *bun.DB
- func (s *Store) DeactivateGrantDefinition(ctx context.Context, uid uuid.UUID) error
- func (s *Store) DeleteDatabase(ctx context.Context, uid uuid.UUID) error
- func (s *Store) DeleteUser(ctx context.Context, uid uuid.UUID) error
- func (s *Store) DeleteUserIdentity(ctx context.Context, uid uuid.UUID) error
- func (s *Store) DenyGrantRequest(ctx context.Context, uid, decidedBy uuid.UUID, reason string) (*GrantRequest, error)
- func (s *Store) DropAllTables(ctx context.Context) error
- func (s *Store) EnsureDefaultAdmin(ctx context.Context, passwordHash string) error
- func (s *Store) GetAPIKeyByID(ctx context.Context, id uuid.UUID) (*APIKey, error)
- func (s *Store) GetAPIKeyByPrefix(ctx context.Context, prefix string) (*APIKey, error)
- func (s *Store) GetActiveGrant(ctx context.Context, userID, databaseID uuid.UUID) (*Grant, error)
- func (s *Store) GetDatabaseByName(ctx context.Context, name string) (*Database, error)
- func (s *Store) GetDatabaseByOracleServiceName(ctx context.Context, serviceName string) (*Database, error)
- func (s *Store) GetDatabaseByUID(ctx context.Context, uid uuid.UUID) (*Database, error)
- func (s *Store) GetGrantByUID(ctx context.Context, uid uuid.UUID) (*Grant, error)
- func (s *Store) GetGrantDefinition(ctx context.Context, uid uuid.UUID) (*GrantDefinition, error)
- func (s *Store) GetGrantRequest(ctx context.Context, uid uuid.UUID) (*GrantRequest, error)
- func (s *Store) GetQuery(ctx context.Context, uid uuid.UUID) (*Query, error)
- func (s *Store) GetQueryRows(ctx context.Context, queryUID uuid.UUID, cursor string, limit int) (*QueryRowsResult, error)
- func (s *Store) GetQueryWithRows(ctx context.Context, uid uuid.UUID) (*QueryWithRows, error)
- func (s *Store) GetUserByIdentity(ctx context.Context, provider, providerID string) (*User, error)
- func (s *Store) GetUserByUID(ctx context.Context, uid uuid.UUID) (*User, error)
- func (s *Store) GetUserByUsername(ctx context.Context, username string) (*User, error)
- func (s *Store) GetUserIdentities(ctx context.Context, userID uuid.UUID) ([]UserIdentity, error)
- func (s *Store) GetUserIdentity(ctx context.Context, uid uuid.UUID) (*UserIdentity, error)
- func (s *Store) HasPendingRequest(ctx context.Context, userID, definitionID, databaseID uuid.UUID) (bool, error)
- func (s *Store) Health(ctx context.Context) error
- func (s *Store) IncrementAPIKeyUsage(ctx context.Context, id uuid.UUID) error
- func (s *Store) IncrementConnectionStats(ctx context.Context, uid uuid.UUID, bytes int64) error
- func (s *Store) ListAPIKeys(ctx context.Context, filter APIKeyFilter) ([]APIKey, error)
- func (s *Store) ListAuditEvents(ctx context.Context, filter AuditFilter) ([]AuditEvent, error)
- func (s *Store) ListConnections(ctx context.Context, filter ConnectionFilter) ([]Connection, error)
- func (s *Store) ListDatabases(ctx context.Context) ([]Database, error)
- func (s *Store) ListGrantDefinitions(ctx context.Context, filter GrantDefinitionFilter) ([]GrantDefinition, error)
- func (s *Store) ListGrantRequests(ctx context.Context, filter GrantRequestFilter) ([]GrantRequest, error)
- func (s *Store) ListGrants(ctx context.Context, filter GrantFilter) ([]Grant, error)
- func (s *Store) ListQueries(ctx context.Context, filter QueryFilter) ([]Query, error)
- func (s *Store) ListUsers(ctx context.Context) ([]User, error)
- func (s *Store) LogAuditEvent(ctx context.Context, event *AuditEvent) error
- func (s *Store) MatchesStorageDSN(host string, port int, databaseName string) bool
- func (s *Store) Migrate(ctx context.Context) error
- func (s *Store) MigrationStatus(ctx context.Context) ([]MigrationInfo, error)
- func (s *Store) RevokeAPIKey(ctx context.Context, id uuid.UUID, revokedBy uuid.UUID) error
- func (s *Store) RevokeGrant(ctx context.Context, uid uuid.UUID, revokedBy uuid.UUID) error
- func (s *Store) Rollback(ctx context.Context) error
- func (s *Store) SetAuthCache(authCache *cache.AuthCache)
- func (s *Store) SetGrantRequestSlackMessage(ctx context.Context, uid uuid.UUID, channel, ts string) error
- func (s *Store) StoreQueryRows(ctx context.Context, queryUID uuid.UUID, rows []QueryRow) error
- func (s *Store) UpdateConnectionActivity(ctx context.Context, uid uuid.UUID) error
- func (s *Store) UpdateDatabase(ctx context.Context, uid uuid.UUID, updates DatabaseUpdate, ...) error
- func (s *Store) UpdateGrantDefinition(ctx context.Context, def *GrantDefinition) error
- func (s *Store) UpdateQueryCompletion(ctx context.Context, uid uuid.UUID, durationMs *float64, rowsAffected *int64, ...) error
- func (s *Store) UpdateUser(ctx context.Context, uid uuid.UUID, updates UserUpdate) error
- func (s *Store) VerifyAPIKey(ctx context.Context, plainKey string) (*APIKey, error)
- type User
- type UserIdentity
- type UserUpdate
Constants ¶
const ( // APIKeyPrefix is the prefix for regular API keys APIKeyPrefix = "dbb_" // WebKeyPrefix is the prefix for web session keys WebKeyPrefix = "web_" // APIKeyRandomLength is the length of the random part of the key APIKeyRandomLength = 32 // APIKeyPrefixLength is the length of the prefix stored for identification APIKeyPrefixLength = 8 // WebSessionMaxDuration is the maximum duration for web sessions (1 hour) WebSessionMaxDuration = time.Hour )
API key constants
const ( RoleAdmin = "admin" RoleViewer = "viewer" RoleConnector = "connector" )
Role constants for user authorization
const ( ControlReadOnly = "read_only" ControlBlockCopy = "block_copy" ControlBlockDDL = "block_ddl" )
Control constants for grant restrictions
const ( ProtocolPostgreSQL = "postgresql" ProtocolOracle = "oracle" ProtocolMySQL = "mysql" ProtocolMariaDB = "mariadb" )
Protocol constants for database connections
const ( KeyTypeAPI = "api" // Regular API key (dbb_ prefix) KeyTypeWeb = "web" // Web session key (web_ prefix) )
API key type constants
const ( // MaxQueryRowsLimit is the maximum number of rows that can be returned per request MaxQueryRowsLimit = 1000 // MaxQueryRowsDataSize is the maximum data size (1MB) that can be returned per request MaxQueryRowsDataSize = 1024 * 1024 // DefaultQueryRowsLimit is the default number of rows returned if not specified DefaultQueryRowsLimit = 100 )
const (
IdentityTypeSlack = "slack"
)
Identity provider constants
Variables ¶
var ( ErrAPIKeyNotFound = errors.New("API key not found") ErrAPIKeyRevoked = errors.New("API key has been revoked") ErrAPIKeyExpired = errors.New("API key has expired") ErrAPIKeyTooShort = errors.New("API key too short") )
API key errors
var ( ErrUserNotFound = errors.New("user not found") ErrDatabaseNotFound = errors.New("database not found") ErrGrantNotFound = errors.New("grant not found") ErrNoActiveGrant = errors.New("no active grant found") ErrGrantAlreadyRevoked = errors.New("grant not found or already revoked") ErrConnectionNotFound = errors.New("connection not found or already closed") ErrQueryNotFound = errors.New("query not found") ErrInvalidCursor = errors.New("invalid cursor") ErrTargetMatchesStorage = errors.New("target database cannot match DBBat storage database") ErrIdentityNotFound = errors.New("identity not found") ErrOAuthStateNotFound = errors.New("oauth state not found") )
Store errors.
var ErrDefinitionInactive = errors.New("grant definition is no longer active")
ErrDefinitionInactive is returned by ApproveGrantRequest if the referenced definition has been deactivated between request and approval.
var ErrGrantDefinitionDuplicate = errors.New("grant definition with this name already exists")
ErrGrantDefinitionDuplicate is returned when an admin tries to create a definition whose name conflicts with an existing active definition.
var ErrGrantDefinitionNotFound = errors.New("grant definition not found")
ErrGrantDefinitionNotFound is returned when a grant definition lookup misses.
var ErrGrantRequestNotFound = errors.New("grant request not found")
ErrGrantRequestNotFound is returned when a request UID misses.
var ErrInvalidTransition = errors.New("grant request not pending")
ErrInvalidTransition is returned when a state transition is rejected because the request is not in `pending`.
var ValidControls = []string{ ControlReadOnly, ControlBlockCopy, ControlBlockDDL, }
ValidControls lists all valid control values
Functions ¶
func ExtractSourceIP ¶
ExtractSourceIP extracts the IP address from a net.Addr
func IsMySQLFamily ¶ added in v0.7.0
IsMySQLFamily reports whether the given protocol speaks the MySQL wire protocol. The MySQL proxy serves both — they share the same listener, auth plugins, and wire-protocol handling. The distinction matters mostly for upstream connection setup (server version banner, auth plugin negotiation) and for UI labeling.
Types ¶
type APIKey ¶
type APIKey struct {
bun.BaseModel `bun:"table:api_keys,alias:ak"`
ID uuid.UUID `bun:"id,pk,type:uuid,default:gen_random_uuid()" json:"id"`
UserID uuid.UUID `bun:"user_id,notnull,type:uuid" json:"user_id"`
Name string `bun:"name,notnull" json:"name"`
KeyHash string `bun:"key_hash,notnull" json:"-"`
KeyPrefix string `bun:"key_prefix,notnull" json:"key_prefix"`
KeyType string `bun:"key_type,notnull,default:'api'" json:"key_type"`
ExpiresAt *time.Time `bun:"expires_at" json:"expires_at"`
LastUsedAt *time.Time `bun:"last_used_at" json:"last_used_at"`
RequestCount int64 `bun:"request_count,notnull,default:0" json:"request_count"`
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
RevokedAt *time.Time `bun:"revoked_at" json:"revoked_at"`
RevokedBy *uuid.UUID `bun:"revoked_by,type:uuid" json:"revoked_by"`
O5LogonSalt []byte `bun:"o5logon_salt" json:"-"`
O5LogonVerifier []byte `bun:"o5logon_verifier" json:"-"` // encrypted with dbbat master key
}
APIKey represents an API key for authentication
func (*APIKey) IsWebSession ¶
IsWebSession returns true if this is a web session key
type APIKeyFilter ¶
type APIKeyFilter struct {
UserID *uuid.UUID
KeyType *string // Filter by key type (api, web)
IncludeAll bool // Include revoked/expired keys
Limit int
Offset int
}
APIKeyFilter represents filters for listing API keys
type AccessGrant ¶
type AccessGrant struct {
bun.BaseModel `bun:"table:access_grants,alias:ag"`
UID uuid.UUID `bun:"uid,pk,type:uuid,default:gen_random_uuid()" json:"uid"`
UserID uuid.UUID `bun:"user_id,notnull,type:uuid" json:"user_id"`
DatabaseID uuid.UUID `bun:"database_id,notnull,type:uuid" json:"database_id"`
Controls []string `bun:"controls,array" json:"controls"` // Array of controls: read_only, block_copy, block_ddl
GrantedBy uuid.UUID `bun:"granted_by,notnull,type:uuid" json:"granted_by"`
StartsAt time.Time `bun:"starts_at,notnull" json:"starts_at"`
ExpiresAt time.Time `bun:"expires_at,notnull" json:"expires_at"`
RevokedAt *time.Time `bun:"revoked_at" json:"revoked_at"`
RevokedBy *uuid.UUID `bun:"revoked_by,type:uuid" json:"revoked_by"`
MaxQueryCounts *int64 `bun:"max_query_counts" json:"max_query_counts"`
MaxBytesTransferred *int64 `bun:"max_bytes_transferred" json:"max_bytes_transferred"`
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
// Computed fields (not stored in DB)
QueryCount int64 `bun:"-" json:"query_count"`
BytesTransferred int64 `bun:"-" json:"bytes_transferred"`
}
AccessGrant represents an access grant
func (*AccessGrant) HasControl ¶
func (g *AccessGrant) HasControl(control string) bool
HasControl checks if the grant has a specific control enabled
func (*AccessGrant) IsReadOnly ¶
func (g *AccessGrant) IsReadOnly() bool
IsReadOnly returns true if the grant has read_only control
func (*AccessGrant) ShouldBlockCopy ¶
func (g *AccessGrant) ShouldBlockCopy() bool
ShouldBlockCopy returns true if COPY commands should be blocked
func (*AccessGrant) ShouldBlockDDL ¶
func (g *AccessGrant) ShouldBlockDDL() bool
ShouldBlockDDL returns true if DDL commands should be blocked
type AuditFilter ¶
type AuditFilter struct {
EventType *string
UserID *uuid.UUID
PerformedBy *uuid.UUID
StartTime *time.Time
EndTime *time.Time
BeforeUID *uuid.UUID // Cursor: return events with UID < this value
Limit int
Offset int
}
AuditFilter represents filters for listing audit events
type AuditLog ¶
type AuditLog struct {
bun.BaseModel `bun:"table:audit_log,alias:al"`
UID uuid.UUID `bun:"uid,pk,type:uuid" json:"uid"` // UUIDv7 set in Go
EventType string `bun:"event_type,notnull" json:"event_type"`
UserID *uuid.UUID `bun:"user_id,type:uuid" json:"user_id"`
PerformedBy *uuid.UUID `bun:"performed_by,type:uuid" json:"performed_by"`
Details json.RawMessage `bun:"details,type:jsonb" json:"details"`
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
}
AuditLog represents an audit log entry
type Connection ¶
type Connection struct {
bun.BaseModel `bun:"table:connections,alias:c"`
UID uuid.UUID `bun:"uid,pk,type:uuid" json:"uid"` // UUIDv7 set in Go
UserID uuid.UUID `bun:"user_id,notnull,type:uuid" json:"user_id"`
DatabaseID uuid.UUID `bun:"database_id,notnull,type:uuid" json:"database_id"`
SourceIP string `bun:"source_ip,notnull,type:inet" json:"source_ip"`
ConnectedAt time.Time `bun:"connected_at,notnull,default:current_timestamp" json:"connected_at"`
LastActivityAt time.Time `bun:"last_activity_at,notnull,default:current_timestamp" json:"last_activity_at"`
DisconnectedAt *time.Time `bun:"disconnected_at" json:"disconnected_at"`
Queries int64 `bun:"queries,notnull,default:0" json:"queries"`
BytesTransferred int64 `bun:"bytes_transferred,notnull,default:0" json:"bytes_transferred"`
}
Connection represents a connection through the proxy
type ConnectionFilter ¶
type ConnectionFilter struct {
UserID *uuid.UUID
DatabaseID *uuid.UUID
BeforeUID *uuid.UUID // Cursor: return connections with UID < this value
Limit int
Offset int
}
ConnectionFilter represents filters for listing connections
type DSNComponents ¶
DSNComponents holds parsed PostgreSQL DSN components for comparison
type Database ¶
type Database struct {
bun.BaseModel `bun:"table:databases,alias:d"`
UID uuid.UUID `bun:"uid,pk,type:uuid,default:gen_random_uuid()" json:"uid"`
Name string `bun:"name,notnull,unique" json:"name"`
Description string `bun:"description" json:"description"`
Host string `bun:"host,notnull" json:"host"`
Port int `bun:"port,notnull" json:"port"`
DatabaseName string `bun:"database_name,notnull" json:"database_name"`
Username string `bun:"username,notnull" json:"username"`
Password string `bun:"-" json:"-"` // Decrypted, not stored
PasswordEncrypted []byte `bun:"password_encrypted,notnull" json:"-"` // Encrypted form
SSLMode string `bun:"ssl_mode,notnull,default:'prefer'" json:"ssl_mode"`
Protocol string `bun:"protocol,notnull,default:'postgresql'" json:"protocol"`
OracleServiceName *string `bun:"oracle_service_name" json:"oracle_service_name,omitempty"`
CreatedBy *uuid.UUID `bun:"created_by,type:uuid" json:"created_by"`
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp" json:"updated_at"`
DeletedAt *time.Time `bun:"deleted_at,soft_delete" json:"-"`
}
Database represents a target database configuration
func (*Database) DecryptPassword ¶
DecryptPassword decrypts a database password using AAD bound to the database UID.
type DatabaseUpdate ¶
type DatabaseUpdate struct {
Description *string
Host *string
Port *int
DatabaseName *string
Username *string
Password *string // Plaintext password to encrypt
SSLMode *string
Protocol *string
OracleServiceName *string
}
DatabaseUpdate represents fields that can be updated
type Grant ¶
type Grant = AccessGrant
Grant is an alias for backward compatibility
func BuildGrantFromDefinition ¶ added in v0.10.0
func BuildGrantFromDefinition(def *GrantDefinition, userID, databaseID, grantedBy uuid.UUID, now time.Time) *Grant
BuildGrantFromDefinition assembles an AccessGrant from a GrantDefinition + the requesting user/database, anchoring the time window to `now`. Used by both the grant-request approval path and any future admin shortcut that wants to materialize a definition into a concrete grant.
type GrantDefinition ¶ added in v0.10.0
type GrantDefinition struct {
bun.BaseModel `bun:"table:grant_definitions,alias:gd"`
UID uuid.UUID `bun:"uid,pk,type:uuid,default:gen_random_uuid()" json:"uid"`
Name string `bun:"name,notnull" json:"name"`
Description string `bun:"description,notnull,default:''" json:"description"`
DurationSeconds int64 `bun:"duration_seconds,notnull" json:"duration_seconds"`
Controls []string `bun:"controls,array,notnull,default:'{}'" json:"controls"`
MaxQueryCounts *int64 `bun:"max_query_counts" json:"max_query_counts"`
MaxBytesTransferred *int64 `bun:"max_bytes_transferred" json:"max_bytes_transferred"`
IsActive bool `bun:"is_active,notnull,default:true" json:"is_active"`
CreatedBy uuid.UUID `bun:"created_by,notnull,type:uuid" json:"created_by"`
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
}
GrantDefinition is an admin-managed template describing the *shape* of a grant: name, duration, controls, optional quotas. Grant requests (separately implemented) reference a definition; on approval, a real AccessGrant is built from the definition + the request's user/database.
Direct admin grant creation bypasses definitions — they exist to bound what users can self-request, not to constrain admins.
type GrantDefinitionFilter ¶ added in v0.10.0
type GrantDefinitionFilter struct {
ActiveOnly bool
}
GrantDefinitionFilter narrows ListGrantDefinitions queries.
type GrantFilter ¶
GrantFilter represents filters for listing grants
type GrantRequest ¶ added in v0.10.0
type GrantRequest struct {
bun.BaseModel `bun:"table:grant_requests,alias:gr"`
UID uuid.UUID `bun:"uid,pk,type:uuid,default:gen_random_uuid()" json:"uid"`
UserID uuid.UUID `bun:"user_id,notnull,type:uuid" json:"user_id"`
GrantDefinitionID uuid.UUID `bun:"grant_definition_id,notnull,type:uuid" json:"grant_definition_id"`
DatabaseID uuid.UUID `bun:"database_id,notnull,type:uuid" json:"database_id"`
Justification string `bun:"justification,notnull,default:''" json:"justification"`
Status GrantRequestStatus `bun:"status,notnull" json:"status"`
RequestedAt time.Time `bun:"requested_at,notnull,default:current_timestamp" json:"requested_at"`
DecidedAt *time.Time `bun:"decided_at" json:"decided_at,omitempty"`
DecidedBy *uuid.UUID `bun:"decided_by,type:uuid" json:"decided_by,omitempty"`
DecisionReason *string `bun:"decision_reason" json:"decision_reason,omitempty"`
ResultingGrantID *uuid.UUID `bun:"resulting_grant_id,type:uuid" json:"resulting_grant_id,omitempty"`
// Slack bookkeeping — populated by the notifier (Spec 04). JSON-omitted
// because the API has no need to expose Slack message coordinates.
SlackChannel *string `bun:"slack_channel" json:"-"`
SlackMessageTS *string `bun:"slack_message_ts" json:"-"`
}
GrantRequest is a user-initiated request for a grant of a particular shape (definition) on a particular database. Admins approve or deny. On approval the system materializes a real AccessGrant from the definition + the request's user/database.
type GrantRequestFilter ¶ added in v0.10.0
type GrantRequestFilter struct {
UserID *uuid.UUID
Status *GrantRequestStatus
DatabaseID *uuid.UUID
Limit int
Offset int
}
GrantRequestFilter narrows ListGrantRequests queries.
type GrantRequestStatus ¶ added in v0.10.0
type GrantRequestStatus string
GrantRequestStatus enumerates the lifecycle states a request can be in.
const ( GrantRequestPending GrantRequestStatus = "pending" GrantRequestApproved GrantRequestStatus = "approved" GrantRequestDenied GrantRequestStatus = "denied" GrantRequestCancelled GrantRequestStatus = "cancelled" //nolint:misspell // matches DB CHECK constraint GrantRequestExpired GrantRequestStatus = "expired" )
Lifecycle states for grant requests. Keep these constants matching the DB CHECK constraint values exactly.
type MigrationInfo ¶
MigrationInfo contains information about a migration
type OAuthState ¶ added in v0.4.0
type OAuthState struct {
bun.BaseModel `bun:"table:oauth_states,alias:os"`
UID uuid.UUID `bun:"uid,pk,type:uuid,default:gen_random_uuid()" json:"uid"`
State string `bun:"state,notnull,unique" json:"state"`
Provider string `bun:"provider,notnull" json:"provider"`
RedirectURL string `bun:"redirect_url" json:"redirect_url,omitempty"`
Metadata json.RawMessage `bun:"metadata,type:jsonb" json:"metadata,omitempty"`
ExpiresAt time.Time `bun:"expires_at,notnull" json:"expires_at"`
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
}
OAuthState represents a temporary OAuth state for CSRF protection
type Options ¶
type Options struct {
// DropTablesFirst drops all tables before running migrations (for test mode)
DropTablesFirst bool
}
Options configures Store creation.
type Query ¶
type Query struct {
bun.BaseModel `bun:"table:queries,alias:q"`
UID uuid.UUID `bun:"uid,pk,type:uuid" json:"uid"` // UUIDv7 set in Go
ConnectionID uuid.UUID `bun:"connection_id,notnull,type:uuid" json:"connection_id"`
SQLText string `bun:"sql_text,notnull" json:"sql_text"`
Parameters *QueryParameters `bun:"parameters,type:jsonb" json:"parameters,omitempty"`
ExecutedAt time.Time `bun:"executed_at,notnull,default:current_timestamp" json:"executed_at"`
DurationMs *float64 `bun:"duration_ms,type:numeric(10,3)" json:"duration_ms"`
RowsAffected *int64 `bun:"rows_affected" json:"rows_affected"`
Error *string `bun:"error" json:"error"`
CopyFormat *string `bun:"copy_format" json:"copy_format,omitempty"` // 'text', 'csv', 'binary', or nil for non-COPY
CopyDirection *string `bun:"copy_direction" json:"copy_direction,omitempty"` // 'in', 'out', or nil for non-COPY
}
Query represents a query execution record
type QueryFilter ¶
type QueryFilter struct {
ConnectionID *uuid.UUID
UserID *uuid.UUID
DatabaseID *uuid.UUID
StartTime *time.Time
EndTime *time.Time
BeforeUID *uuid.UUID // Cursor: return queries with UID < this value (for stable pagination)
Limit int
Offset int
}
QueryFilter represents filters for listing queries
type QueryParameters ¶
type QueryParameters struct {
Values []string `json:"values"` // Decoded string representation
Raw []string `json:"raw,omitempty"` // Base64-encoded raw bytes
FormatCodes []int16 `json:"format_codes,omitempty"` // 0=text, 1=binary
TypeOIDs []uint32 `json:"type_oids,omitempty"` // PostgreSQL type OIDs
}
QueryParameters stores parameter values for prepared statements
type QueryRow ¶
type QueryRow struct {
RowNumber int `json:"row_number"`
RowData json.RawMessage `json:"row_data"`
RowSizeBytes int64 `json:"row_size_bytes"`
}
QueryRow is an alias for API compatibility (without bun.BaseModel for simpler usage)
type QueryRowModel ¶
type QueryRowModel struct {
bun.BaseModel `bun:"table:query_rows,alias:qr"`
UID uuid.UUID `bun:"uid,pk,type:uuid" json:"uid"` // UUIDv7 set in Go
QueryID uuid.UUID `bun:"query_id,notnull,type:uuid" json:"query_id"`
RowNumber int `bun:"row_number,notnull" json:"row_number"`
RowData json.RawMessage `bun:"row_data,notnull,type:jsonb" json:"row_data"`
RowSizeBytes int64 `bun:"row_size_bytes,notnull" json:"row_size_bytes"`
}
QueryRowModel represents a single row from query results or COPY data
type QueryRowsCursor ¶
type QueryRowsCursor struct {
Offset int64 `json:"offset"`
}
QueryRowsCursor represents the pagination cursor state
type QueryRowsResult ¶
type QueryRowsResult struct {
Rows []QueryRow `json:"rows"`
NextCursor string `json:"next_cursor,omitempty"`
HasMore bool `json:"has_more"`
TotalRows int64 `json:"total_rows"`
}
QueryRowsResult contains paginated query rows
type QueryWithRows ¶
QueryWithRows combines a query with its result rows
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store provides access to the database
func (*Store) ApproveGrantRequest ¶ added in v0.10.0
func (s *Store) ApproveGrantRequest(ctx context.Context, uid, decidedBy uuid.UUID) (*Grant, *GrantRequest, error)
ApproveGrantRequest atomically transitions a pending request to approved and creates the resulting AccessGrant from the linked definition. The caller (admin) is captured in decided_by + grant.granted_by.
Returns:
- the resulting grant, the updated request, nil on success
- ErrGrantRequestNotFound if the request doesn't exist
- ErrInvalidTransition if the request isn't pending
- ErrDefinitionInactive if the linked definition was deactivated
Wrapped in a transaction so a partial failure (request flipped, grant not created) can't leak.
func (*Store) CancelGrantRequest ¶ added in v0.10.0
func (s *Store) CancelGrantRequest(ctx context.Context, uid, byUser uuid.UUID) (*GrantRequest, error)
CancelGrantRequest atomically transitions pending → cancelled. Used by the requester themselves; the caller layer enforces who can cancel whose request.
func (*Store) CleanupExpiredOAuthStates ¶ added in v0.4.0
CleanupExpiredOAuthStates removes all expired OAuth states.
func (*Store) CloseConnection ¶
CloseConnection sets the disconnected_at timestamp
func (*Store) ConsumeOAuthState ¶ added in v0.4.0
ConsumeOAuthState retrieves and deletes an OAuth state in one operation. It only matches states that have not yet expired.
func (*Store) CreateAPIKey ¶
func (s *Store) CreateAPIKey(ctx context.Context, userID uuid.UUID, name string, expiresAt *time.Time, encryptionKey ...[]byte) (*APIKey, string, error)
CreateAPIKey creates a new API key for a user. Returns the created APIKey and the plain text key (only shown once). If encryptionKey is provided (non-nil), an O5LOGON verifier is computed and stored for Oracle proxy authentication.
func (*Store) CreateAPIKeyWithValue ¶ added in v0.5.0
func (s *Store) CreateAPIKeyWithValue(ctx context.Context, userID uuid.UUID, name string, plainKey string, expiresAt *time.Time, encryptionKey ...[]byte) (*APIKey, error)
CreateAPIKeyWithValue creates an API key with a specific plaintext value. Used for test mode provisioning where stable, predictable keys are needed. If encryptionKey is provided, an O5LOGON verifier is computed and stored.
func (*Store) CreateConnection ¶
func (s *Store) CreateConnection(ctx context.Context, userID, databaseID uuid.UUID, sourceIP string) (*Connection, error)
CreateConnection creates a new connection record
func (*Store) CreateDatabase ¶
func (s *Store) CreateDatabase(ctx context.Context, db *Database, encryptionKey []byte) (*Database, error)
CreateDatabase creates a new database configuration. It uses a transaction to ensure the password is encrypted with AAD bound to the database UID. Returns ErrTargetMatchesStorage if the target database matches the DBBat storage database.
func (*Store) CreateGrant ¶
CreateGrant creates a new access grant
func (*Store) CreateGrantDefinition ¶ added in v0.10.0
func (s *Store) CreateGrantDefinition(ctx context.Context, def *GrantDefinition) (*GrantDefinition, error)
CreateGrantDefinition inserts a new GrantDefinition. The unique-active-name index enforces no two active definitions share a name; deactivated definitions don't block reuse.
func (*Store) CreateGrantRequest ¶ added in v0.10.0
func (s *Store) CreateGrantRequest(ctx context.Context, req *GrantRequest) (*GrantRequest, error)
CreateGrantRequest inserts a new pending request.
func (*Store) CreateOAuthState ¶ added in v0.4.0
func (s *Store) CreateOAuthState(ctx context.Context, state *OAuthState) (*OAuthState, error)
CreateOAuthState persists a new OAuth state for CSRF protection.
func (*Store) CreateQuery ¶
CreateQuery creates a new query record
func (*Store) CreateUser ¶
func (s *Store) CreateUser(ctx context.Context, username, passwordHash string, roles []string) (*User, error)
CreateUser creates a new user with the specified roles
func (*Store) CreateUserIdentity ¶ added in v0.4.0
func (s *Store) CreateUserIdentity(ctx context.Context, identity *UserIdentity) (*UserIdentity, error)
CreateUserIdentity creates a new user identity link.
func (*Store) CreateWebSession ¶
CreateWebSession creates a new web session key for a user Web sessions have a fixed 1-hour expiration and use the web_ prefix Returns the created APIKey and the plain text key (only shown once)
func (*Store) DeactivateGrantDefinition ¶ added in v0.10.0
DeactivateGrantDefinition flips is_active to false. We never hard-delete because grant_requests reference definitions and we want the historical trail to stay intact.
func (*Store) DeleteDatabase ¶
DeleteDatabase deletes a database
func (*Store) DeleteUser ¶
DeleteUser deletes a user
func (*Store) DeleteUserIdentity ¶ added in v0.4.0
DeleteUserIdentity soft-deletes a user identity.
func (*Store) DenyGrantRequest ¶ added in v0.10.0
func (s *Store) DenyGrantRequest(ctx context.Context, uid, decidedBy uuid.UUID, reason string) (*GrantRequest, error)
DenyGrantRequest atomically transitions pending → denied with an optional reason.
func (*Store) DropAllTables ¶
DropAllTables drops all application tables and types (for test mode) This should be called BEFORE migrations to ensure a fresh start
func (*Store) EnsureDefaultAdmin ¶
EnsureDefaultAdmin creates a default admin user if no users exist
func (*Store) GetAPIKeyByID ¶
GetAPIKeyByID retrieves an API key by its ID
func (*Store) GetAPIKeyByPrefix ¶
GetAPIKeyByPrefix retrieves all API keys with a given prefix Since prefix is unique, this returns at most one key
func (*Store) GetActiveGrant ¶
GetActiveGrant retrieves an active grant for a user and database
func (*Store) GetDatabaseByName ¶
GetDatabaseByName retrieves a database by name
func (*Store) GetDatabaseByOracleServiceName ¶ added in v0.4.0
func (s *Store) GetDatabaseByOracleServiceName(ctx context.Context, serviceName string) (*Database, error)
GetDatabaseByOracleServiceName retrieves an Oracle database by its service name.
func (*Store) GetDatabaseByUID ¶
GetDatabaseByUID retrieves a database by UID
func (*Store) GetGrantByUID ¶
GetGrantByUID retrieves a grant by UID
func (*Store) GetGrantDefinition ¶ added in v0.10.0
GetGrantDefinition fetches a definition by UID. Returns ErrGrantDefinitionNotFound if the row doesn't exist.
func (*Store) GetGrantRequest ¶ added in v0.10.0
GetGrantRequest fetches a request by UID.
func (*Store) GetQueryRows ¶
func (s *Store) GetQueryRows(ctx context.Context, queryUID uuid.UUID, cursor string, limit int) (*QueryRowsResult, error)
GetQueryRows retrieves paginated rows for a query with cursor-based pagination
func (*Store) GetQueryWithRows ¶
GetQueryWithRows retrieves a query with its result rows
func (*Store) GetUserByIdentity ¶ added in v0.4.0
GetUserByIdentity retrieves a user by their external identity (provider + provider_id).
func (*Store) GetUserByUID ¶
GetUserByUID retrieves a user by UID
func (*Store) GetUserByUsername ¶
GetUserByUsername retrieves a user by username
func (*Store) GetUserIdentities ¶ added in v0.4.0
GetUserIdentities retrieves all identities for a given user.
func (*Store) GetUserIdentity ¶ added in v0.4.0
GetUserIdentity retrieves a single user identity by UID.
func (*Store) HasPendingRequest ¶ added in v0.10.0
func (s *Store) HasPendingRequest(ctx context.Context, userID, definitionID, databaseID uuid.UUID) (bool, error)
HasPendingRequest checks whether a user already has an open request for the same database+definition. Used by the API to short-circuit duplicates before they get persisted.
func (*Store) IncrementAPIKeyUsage ¶
IncrementAPIKeyUsage updates the last_used_at and increments request_count
func (*Store) IncrementConnectionStats ¶
IncrementConnectionStats increments the query count by 1 and adds bytes to bytes_transferred
func (*Store) ListAPIKeys ¶
ListAPIKeys retrieves API keys with optional filters
func (*Store) ListAuditEvents ¶
func (s *Store) ListAuditEvents(ctx context.Context, filter AuditFilter) ([]AuditEvent, error)
ListAuditEvents retrieves audit events with optional filters
func (*Store) ListConnections ¶
func (s *Store) ListConnections(ctx context.Context, filter ConnectionFilter) ([]Connection, error)
ListConnections retrieves connections with optional filters
func (*Store) ListDatabases ¶
ListDatabases retrieves all databases
func (*Store) ListGrantDefinitions ¶ added in v0.10.0
func (s *Store) ListGrantDefinitions(ctx context.Context, filter GrantDefinitionFilter) ([]GrantDefinition, error)
ListGrantDefinitions returns definitions matching the filter. Admins want `ActiveOnly=false` to see soft-deleted entries; the request UI passes `ActiveOnly=true`.
func (*Store) ListGrantRequests ¶ added in v0.10.0
func (s *Store) ListGrantRequests(ctx context.Context, filter GrantRequestFilter) ([]GrantRequest, error)
ListGrantRequests returns requests matching the filter, newest first.
func (*Store) ListGrants ¶
ListGrants retrieves grants with optional filters
func (*Store) ListQueries ¶
ListQueries retrieves queries with optional filters
func (*Store) LogAuditEvent ¶
func (s *Store) LogAuditEvent(ctx context.Context, event *AuditEvent) error
LogAuditEvent creates a new audit log entry
func (*Store) MatchesStorageDSN ¶
MatchesStorageDSN checks if a target database configuration matches the storage DSN. Returns true if the target appears to be the same database as DBBat storage.
func (*Store) MigrationStatus ¶
func (s *Store) MigrationStatus(ctx context.Context) ([]MigrationInfo, error)
MigrationStatus returns the status of all migrations
func (*Store) RevokeAPIKey ¶
RevokeAPIKey revokes an API key
func (*Store) RevokeGrant ¶
RevokeGrant revokes a grant
func (*Store) SetAuthCache ¶ added in v0.1.0
SetAuthCache sets the authentication cache for API key verification.
func (*Store) SetGrantRequestSlackMessage ¶ added in v0.10.0
func (s *Store) SetGrantRequestSlackMessage(ctx context.Context, uid uuid.UUID, channel, ts string) error
SetGrantRequestSlackMessage records the channel/ts of the Slack post that announced this request, so the notifier can chat.update on status changes (Spec 04). NULL on either column means "no Slack post for this request" (notifier disabled, or first post failed).
func (*Store) StoreQueryRows ¶
StoreQueryRows stores result rows for a query
func (*Store) UpdateConnectionActivity ¶
UpdateConnectionActivity updates the last_activity_at timestamp
func (*Store) UpdateDatabase ¶
func (s *Store) UpdateDatabase(ctx context.Context, uid uuid.UUID, updates DatabaseUpdate, encryptionKey []byte) error
UpdateDatabase updates a database. Returns ErrTargetMatchesStorage if the update would cause the target to match the DBBat storage database.
func (*Store) UpdateGrantDefinition ¶ added in v0.10.0
func (s *Store) UpdateGrantDefinition(ctx context.Context, def *GrantDefinition) error
UpdateGrantDefinition mutates an existing definition. Only the admin-editable fields are touched; uid / created_by / created_at / is_active stay put. Use DeactivateGrantDefinition for the lifecycle flip.
func (*Store) UpdateQueryCompletion ¶ added in v0.4.0
func (s *Store) UpdateQueryCompletion(ctx context.Context, uid uuid.UUID, durationMs *float64, rowsAffected *int64, queryError *string) error
UpdateQueryCompletion updates a query with duration, rows affected, and error.
func (*Store) UpdateUser ¶
UpdateUser updates a user
type User ¶
type User struct {
bun.BaseModel `bun:"table:users,alias:u"`
UID uuid.UUID `bun:"uid,pk,type:uuid,default:gen_random_uuid()" json:"uid"`
Username string `bun:"username,notnull,unique" json:"username"`
PasswordHash string `bun:"password_hash,notnull" json:"-"`
Roles []string `bun:"roles,array" json:"roles"`
RateLimitExempt bool `bun:"rate_limit_exempt,notnull,default:false" json:"rate_limit_exempt"`
PasswordChangedAt *time.Time `bun:"password_changed_at" json:"-"`
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp" json:"updated_at"`
DeletedAt *time.Time `bun:"deleted_at,soft_delete" json:"-"`
}
User represents a DBBat user
func (*User) HasChangedPassword ¶
HasChangedPassword returns true if the user has changed their initial password
func (*User) IsConnector ¶
IsConnector returns true if the user has the connector role
type UserIdentity ¶ added in v0.4.0
type UserIdentity struct {
bun.BaseModel `bun:"table:user_identities,alias:ui"`
UID uuid.UUID `bun:"uid,pk,type:uuid,default:gen_random_uuid()" json:"uid"`
UserID uuid.UUID `bun:"user_id,notnull,type:uuid" json:"user_id"`
Provider string `bun:"provider,notnull" json:"provider"`
ProviderID string `bun:"provider_id,notnull" json:"provider_id"`
Email string `bun:"email" json:"email,omitempty"`
DisplayName string `bun:"display_name" json:"display_name,omitempty"`
Metadata json.RawMessage `bun:"metadata,type:jsonb" json:"metadata,omitempty"`
CreatedAt time.Time `bun:"created_at,notnull,default:current_timestamp" json:"created_at"`
UpdatedAt time.Time `bun:"updated_at,notnull,default:current_timestamp" json:"updated_at"`
DeletedAt *time.Time `bun:"deleted_at,soft_delete" json:"-"`
}
UserIdentity represents a link between a user and an external identity provider
type UserUpdate ¶
UserUpdate represents fields that can be updated