Documentation
¶
Index ¶
- Variables
- func ClassifyPostgresError(err error) error
- func EnrichChatErrorBody(body []byte, needsRefresh bool, since time.Time, workspaceID string) []byte
- func ProbeModelsAnon(c *gin.Context)
- func WaitUntilIdle(ctx context.Context, workspaceID string, tracker *sse.Tracker, ...) error
- type AdminProviderCredentialsHandler
- func (h *AdminProviderCredentialsHandler) Create(c *gin.Context)
- func (h *AdminProviderCredentialsHandler) CreateAutoApply(c *gin.Context)
- func (h *AdminProviderCredentialsHandler) Delete(c *gin.Context)
- func (h *AdminProviderCredentialsHandler) DeleteAutoApply(c *gin.Context)
- func (h *AdminProviderCredentialsHandler) Get(c *gin.Context)
- func (h *AdminProviderCredentialsHandler) List(c *gin.Context)
- func (h *AdminProviderCredentialsHandler) ListAutoApply(c *gin.Context)
- func (h *AdminProviderCredentialsHandler) ProbeModels(c *gin.Context)
- func (h *AdminProviderCredentialsHandler) SetAutoApplyStore(s AutoApplyStore)
- func (h *AdminProviderCredentialsHandler) Update(c *gin.Context)
- type AdminSessionHandler
- type AgentReloadHandler
- func (h *AgentReloadHandler) Reload(c *gin.Context)
- func (h *AgentReloadHandler) SetBrokerPublisher(b BrokerPublisher)
- func (h *AgentReloadHandler) SetMetrics(m MetricsRecorder)
- func (h *AgentReloadHandler) SetPasswordGetter(provider interfaces.WorkspacePasswordProvider)
- func (h *AgentReloadHandler) SetQueueClearer(q QueueClearer)
- func (h *AgentReloadHandler) SetSSETracker(t *sse.Tracker)
- type AgentRoleHandler
- func (h *AgentRoleHandler) ClearWorkspaceRole(c *gin.Context)
- func (h *AgentRoleHandler) CreateOrg(c *gin.Context)
- func (h *AgentRoleHandler) CreatePlatform(c *gin.Context)
- func (h *AgentRoleHandler) DeleteOrg(c *gin.Context)
- func (h *AgentRoleHandler) DeletePlatform(c *gin.Context)
- func (h *AgentRoleHandler) GetEffectiveWorkspaceRole(c *gin.Context)
- func (h *AgentRoleHandler) GetOrg(c *gin.Context)
- func (h *AgentRoleHandler) GetPlatform(c *gin.Context)
- func (h *AgentRoleHandler) GetWorkspaceRole(c *gin.Context)
- func (h *AgentRoleHandler) ListOrg(c *gin.Context)
- func (h *AgentRoleHandler) ListPlatform(c *gin.Context)
- func (h *AgentRoleHandler) SetWorkspaceRole(c *gin.Context)
- func (h *AgentRoleHandler) UpdateOrg(c *gin.Context)
- func (h *AgentRoleHandler) UpdatePlatform(c *gin.Context)
- type AgentStateChecker
- type AgentStateStore
- type AuditHandler
- type AutoApplyStore
- type BrokerPublisher
- type BulkReloadHandler
- func (h *BulkReloadHandler) BulkReload(c *gin.Context)
- func (h *BulkReloadHandler) SetBrokerPublisher(b BrokerPublisher)
- func (h *BulkReloadHandler) SetMetrics(m MetricsRecorder)
- func (h *BulkReloadHandler) SetPasswordGetter(provider interfaces.WorkspacePasswordProvider)
- func (h *BulkReloadHandler) SetQueueClearer(q QueueClearer)
- func (h *BulkReloadHandler) SetSSETracker(t *sse.Tracker)
- type Catalog
- type CatalogModel
- type CatalogProvider
- type CredentialBindingStore
- type CredentialResponse
- type CredentialStateWriter
- type CredentialStore
- type DEKUnlocker
- type EmailHandler
- type EmailVerifierAdapter
- type EmailVerifyHandler
- type ErrDrainTimeout
- type InternalOrgStatusHandler
- type InvitationsHandler
- func (h *InvitationsHandler) Accept(c *gin.Context)
- func (h *InvitationsHandler) Create(c *gin.Context)
- func (h *InvitationsHandler) Decline(c *gin.Context)
- func (h *InvitationsHandler) Delete(c *gin.Context)
- func (h *InvitationsHandler) GetByToken(c *gin.Context)
- func (h *InvitationsHandler) List(c *gin.Context)
- func (h *InvitationsHandler) Resend(c *gin.Context)
- func (h *InvitationsHandler) SetCredentialBinder(b orgCredentialBinder)
- func (h *InvitationsHandler) VerifyUserForInvitation(c *gin.Context)
- type KeyRotator
- type LoginDiscoveryHandler
- type MetricsRecorder
- type ModelAvailability
- type ModelCache
- type ModelCatalogParser
- type ModelClient
- type ModelSelectionRecorder
- type ModelSelectionRequest
- type ModelStore
- type ModelsHandler
- func (h *ModelsHandler) ListModels(c *gin.Context)
- func (h *ModelsHandler) SetAgentClient(ac ModelClient)
- func (h *ModelsHandler) SetCatalogParser(p ModelCatalogParser)
- func (h *ModelsHandler) SetLogger(l pkginterfaces.LoggerInterface)
- func (h *ModelsHandler) SetMetricsRecorder(r ModelSelectionRecorder)
- func (h *ModelsHandler) SetModel(c *gin.Context)
- func (h *ModelsHandler) SetModelCache(c ModelCache)
- func (h *ModelsHandler) SetModelStore(s ModelStore)
- func (h *ModelsHandler) SetPolicyChecker(p OrgPolicyChecker)
- func (h *ModelsHandler) SetRelayActive(active bool)
- func (h *ModelsHandler) SetRelayChecker(rc RelayStateChecker)
- type OrgBilling
- type OrgCredentialsHandler
- func (h *OrgCredentialsHandler) Create(c *gin.Context)
- func (h *OrgCredentialsHandler) CreateAutoApply(c *gin.Context)
- func (h *OrgCredentialsHandler) Delete(c *gin.Context)
- func (h *OrgCredentialsHandler) DeleteAutoApply(c *gin.Context)
- func (h *OrgCredentialsHandler) List(c *gin.Context)
- func (h *OrgCredentialsHandler) ListAutoApply(c *gin.Context)
- func (h *OrgCredentialsHandler) ProbeModels(c *gin.Context)
- func (h *OrgCredentialsHandler) Update(c *gin.Context)
- type OrgPolicyChecker
- type OrgsHandler
- func (h *OrgsHandler) AddMember(c *gin.Context)
- func (h *OrgsHandler) ChangeMemberRole(c *gin.Context)
- func (h *OrgsHandler) Checkout(c *gin.Context)
- func (h *OrgsHandler) Create(c *gin.Context)
- func (h *OrgsHandler) Delete(c *gin.Context)
- func (h *OrgsHandler) Get(c *gin.Context)
- func (h *OrgsHandler) GetOrg(ctx context.Context, orgID string) (*types.Organization, error)
- func (h *OrgsHandler) IsOrgAdmin(ctx context.Context, orgID, userID string) (bool, error)
- func (h *OrgsHandler) IsOrgMember(ctx context.Context, orgID, userID string) (bool, error)
- func (h *OrgsHandler) List(c *gin.Context)
- func (h *OrgsHandler) ListMembers(c *gin.Context)
- func (h *OrgsHandler) ListWorkspaces(c *gin.Context)
- func (h *OrgsHandler) Portal(c *gin.Context)
- func (h *OrgsHandler) RemoveMember(c *gin.Context)
- func (h *OrgsHandler) SetBilling(b OrgBilling, successURL, cancelURL, portalURL string)
- func (h *OrgsHandler) SetLogger(l orgsLogger)
- func (h *OrgsHandler) Update(c *gin.Context)
- func (h *OrgsHandler) VerifyMember(c *gin.Context)
- type PasswordHashUpdater
- type PasswordResetHandler
- type PasswordVerifier
- type PendingOrgCleaner
- type PendingReloadLister
- type PlatformAdminHandler
- func (h *PlatformAdminHandler) ListOrgs(c *gin.Context)
- func (h *PlatformAdminHandler) ListUsers(c *gin.Context)
- func (h *PlatformAdminHandler) SuspendOrg(c *gin.Context)
- func (h *PlatformAdminHandler) SuspendUser(c *gin.Context)
- func (h *PlatformAdminHandler) UnsuspendOrg(c *gin.Context)
- func (h *PlatformAdminHandler) UnsuspendUser(c *gin.Context)
- type PodBootstrapHandler
- type PodIPResolver
- type PolicyHandler
- type ProbeModelEntry
- type ProbeModelsRequest
- type ProbeModelsResponse
- type PromptHandler
- func (h *PromptHandler) GetOrg(c *gin.Context)
- func (h *PromptHandler) GetPlatform(c *gin.Context)
- func (h *PromptHandler) GetWorkspacePrompt(c *gin.Context)
- func (h *PromptHandler) SetOrg(c *gin.Context)
- func (h *PromptHandler) SetPlatform(c *gin.Context)
- func (h *PromptHandler) SetWorkspacePrompt(c *gin.Context)
- type ProviderModelCost
- type ProxyHandler
- func (h *ProxyHandler) AbortSession(c *gin.Context)
- func (h *ProxyHandler) BackfillSessionParents(ctx context.Context, workspaceID string)
- func (h *ProxyHandler) CreateSession(c *gin.Context)
- func (h *ProxyHandler) DeleteQueueMessage(c *gin.Context)
- func (h *ProxyHandler) DeleteSession(c *gin.Context)
- func (h *ProxyHandler) EnableSessionParentResolution()
- func (h *ProxyHandler) EnqueueMessage(c *gin.Context)
- func (h *ProxyHandler) GetActiveSessions(ctx context.Context, workspaceID string) []string
- func (h *ProxyHandler) GetAllKnownPhases() map[string]string
- func (h *ProxyHandler) GetBroker() BrokerPublisher
- func (h *ProxyHandler) GetCachedPasswordForTest(workspaceID string) (string, bool)
- func (h *ProxyHandler) GetHistory(c *gin.Context)
- func (h *ProxyHandler) GetMessageQueueService() interfaces.MessageQueueService
- func (h *ProxyHandler) GetPasswordGetter() interfaces.WorkspacePasswordProvider
- func (h *ProxyHandler) GetPriorPhaseForTest(workspaceID string) (string, bool)
- func (h *ProxyHandler) GetSSETracker() *sse.Tracker
- func (h *ProxyHandler) GetSession(c *gin.Context)
- func (h *ProxyHandler) GetWorkspaceConfigForTest(workspaceID string) (wsstate.Config, bool)
- func (h *ProxyHandler) GetWorkspaceOwner(workspaceID string) string
- func (h *ProxyHandler) HasActiveWorkspaceForTest(workspaceID string) bool
- func (h *ProxyHandler) ListPermissions(c *gin.Context)
- func (h *ProxyHandler) ListQuestions(c *gin.Context)
- func (h *ProxyHandler) ListQueue(c *gin.Context)
- func (h *ProxyHandler) ListSessions(c *gin.Context)
- func (h *ProxyHandler) MarkSessionDeletedForTest(workspaceID, sessionID string)
- func (h *ProxyHandler) PermissionReply(c *gin.Context)
- func (h *ProxyHandler) QuestionReject(c *gin.Context)
- func (h *ProxyHandler) QuestionReply(c *gin.Context)
- func (h *ProxyHandler) RenameSessionInAgent(ctx context.Context, workspaceID, sessionID, title string) error
- func (h *ProxyHandler) SendMessage(c *gin.Context)
- func (h *ProxyHandler) SendPromptAsync(c *gin.Context)
- func (h *ProxyHandler) SetActiveSessionsForTest(workspaceID string, sessionIDs []string)
- func (h *ProxyHandler) SetAgentStateChecker(c AgentStateChecker)
- func (h *ProxyHandler) SetCachedPasswordForTest(workspaceID, password string)
- func (h *ProxyHandler) SetMessageQueueService(svc interfaces.MessageQueueService)
- func (h *ProxyHandler) SetMeteringService(svc interfaces.MeteringService)
- func (h *ProxyHandler) SetParentBackfilledForTest(workspaceID string)
- func (h *ProxyHandler) SetPriorPhaseForTest(workspaceID, phase string)
- func (h *ProxyHandler) SetRequestBufferConfig(maxSize int, timeout time.Duration)
- func (h *ProxyHandler) SetSessionIndex(si interfaces.SessionIndexService)
- func (h *ProxyHandler) SetStateStore(store wsstate.Store)
- func (h *ProxyHandler) SetSweepInterval(d time.Duration)
- func (h *ProxyHandler) SetVersionSyncCallback(cb workspace.VersionSyncCallback)
- func (h *ProxyHandler) SetWorkspaceConfigForTest(workspaceID string, cfg wsstate.Config)
- func (h *ProxyHandler) SetWorkspaceUpdateCallback(cb workspace.WorkspaceUpdateCallback)
- func (h *ProxyHandler) Start() error
- func (h *ProxyHandler) Stop() error
- func (h *ProxyHandler) StreamEvents(c *gin.Context)
- func (h *ProxyHandler) StreamUserEvents(c *gin.Context)
- func (h *ProxyHandler) WorkspacePassword(ctx context.Context, workspaceID string) (string, error)
- type QueueClearer
- type RelayAdminHandler
- func (h *RelayAdminHandler) Deploy(c *gin.Context)
- func (h *RelayAdminHandler) GetSetup(c *gin.Context)
- func (h *RelayAdminHandler) GetStatus(c *gin.Context)
- func (h *RelayAdminHandler) Pause(c *gin.Context)
- func (h *RelayAdminHandler) Resume(c *gin.Context)
- func (h *RelayAdminHandler) Rotate(c *gin.Context)
- func (h *RelayAdminHandler) SaveAWSCreds(c *gin.Context)
- func (h *RelayAdminHandler) SaveGCPCreds(c *gin.Context)
- func (h *RelayAdminHandler) SaveOCICreds(c *gin.Context)
- func (h *RelayAdminHandler) SetHTTPClient(client *http.Client)
- type RelayStateChecker
- type RotateKeyHandler
- func (h *RotateKeyHandler) ChangePassword(c *gin.Context)
- func (h *RotateKeyHandler) RecoverAccount(c *gin.Context)
- func (h *RotateKeyHandler) RotateKey(c *gin.Context)
- func (h *RotateKeyHandler) SetAuditFunc(f func(userID, action string))
- func (h *RotateKeyHandler) SetPasswordUpdater(u PasswordHashUpdater)
- type SSOHandler
- func (h *SSOHandler) Callback(c *gin.Context)
- func (h *SSOHandler) Delete(c *gin.Context)
- func (h *SSOHandler) Domains(c *gin.Context)
- func (h *SSOHandler) Get(c *gin.Context)
- func (h *SSOHandler) OIDCEnabled(ctx context.Context) bool
- func (h *SSOHandler) Put(c *gin.Context)
- func (h *SSOHandler) RotateToken(c *gin.Context)
- func (h *SSOHandler) Start(c *gin.Context)
- func (h *SSOHandler) VerifyDomain(c *gin.Context)
- type SecretsHandler
- func (h *SecretsHandler) CreateSecret(c *gin.Context)
- func (h *SecretsHandler) DeleteSecret(c *gin.Context)
- func (h *SecretsHandler) GetAuditLog(c *gin.Context)
- func (h *SecretsHandler) GetBindings(c *gin.Context)
- func (h *SecretsHandler) GetSecret(c *gin.Context)
- func (h *SecretsHandler) GetSecretBindings(c *gin.Context)
- func (h *SecretsHandler) HasPodIPResolver() bool
- func (h *SecretsHandler) ListSecrets(c *gin.Context)
- func (h *SecretsHandler) ReloadSecrets(c *gin.Context)
- func (h *SecretsHandler) RevealSecret(c *gin.Context)
- func (h *SecretsHandler) SetAgentPusher(p *agentpush.Service)
- func (h *SecretsHandler) SetBindings(c *gin.Context)
- func (h *SecretsHandler) SetCredentialStateWriter(w CredentialStateWriter)
- func (h *SecretsHandler) SetLogger(l pkginterfaces.LoggerInterface)
- func (h *SecretsHandler) SetModelCache(c ModelCache)
- func (h *SecretsHandler) SetPasswordVerifier(v PasswordVerifier)
- func (h *SecretsHandler) SetPodIPResolver(r PodIPResolver)
- func (h *SecretsHandler) UpdateSecret(c *gin.Context)
- type SettingsHandler
- func (h *SettingsHandler) GetAdminSettings(c *gin.Context)
- func (h *SettingsHandler) GetAdminSettingsSchema(c *gin.Context)
- func (h *SettingsHandler) GetUserSettings(c *gin.Context)
- func (h *SettingsHandler) GetUserSettingsSchema(c *gin.Context)
- func (h *SettingsHandler) SetAdminSetting(c *gin.Context)
- func (h *SettingsHandler) SetUserSetting(c *gin.Context)
- type StripeWebhookHandler
- type TerminalCache
- type TerminalHandler
- type TerminalMessage
- type TicketResponse
- type TokenReviewer
- type UnlockDEKHandler
- type UsageHandler
- func (h *UsageHandler) AdminBillingStatus(c *gin.Context)
- func (h *UsageHandler) AdminDiscardDLQ(c *gin.Context)
- func (h *UsageHandler) AdminGetDLQ(c *gin.Context)
- func (h *UsageHandler) AdminGetUsage(c *gin.Context)
- func (h *UsageHandler) AdminRetryDLQ(c *gin.Context)
- func (h *UsageHandler) GetQuotaStatus(c *gin.Context)
- func (h *UsageHandler) GetUsage(c *gin.Context)
- func (h *UsageHandler) GetWorkspaceUsage(c *gin.Context)
- func (h *UsageHandler) SetDB(db *sql.DB)
- type UserProviderCredentialsHandler
- func (h *UserProviderCredentialsHandler) Bind(c *gin.Context)
- func (h *UserProviderCredentialsHandler) Create(c *gin.Context)
- func (h *UserProviderCredentialsHandler) Delete(c *gin.Context)
- func (h *UserProviderCredentialsHandler) Get(c *gin.Context)
- func (h *UserProviderCredentialsHandler) List(c *gin.Context)
- func (h *UserProviderCredentialsHandler) ListBindings(c *gin.Context)
- func (h *UserProviderCredentialsHandler) ProbeModels(c *gin.Context)
- func (h *UserProviderCredentialsHandler) SetCredentialStateWriter(w CredentialStateWriter)
- func (h *UserProviderCredentialsHandler) SetWorkspaceOwnerChecker(fn WorkspaceOwnerChecker)
- func (h *UserProviderCredentialsHandler) Unbind(c *gin.Context)
- type WorkspaceEnvHandler
- type WorkspaceEnvService
- type WorkspaceGetter
- type WorkspaceOwnerChecker
- type WorkspaceServicer
Constants ¶
This section is empty.
Variables ¶
var ( ErrDuplicateCredential = errors.New("duplicate credential") ErrCredentialNotFound = errors.New("credential not found") // ErrCredentialCheckViolation is returned when an INSERT/UPDATE // trips a CHECK constraint (PG SQLSTATE 23514). With Epic 55 the // `kind` and `slug` columns carry CHECK constraints; the handler // layer should ideally have caught the bad input via ValidateKind / // ValidateSlug at the boundary, so reaching this code is a sign // that boundary validation drifted from the DB constraint set // (the property tests in credential_identity_test.go guard against // that drift). Map to 400 anyway as defense-in-depth — the user // sent invalid data, not a server fault. ErrCredentialCheckViolation = errors.New("credential failed validation") )
Functions ¶
func ClassifyPostgresError ¶
func EnrichChatErrorBody ¶
func EnrichChatErrorBody( body []byte, needsRefresh bool, since time.Time, workspaceID string, ) []byte
EnrichChatErrorBody adds agentNeedsRefresh hint to error responses when the workspace has staged credentials.
func ProbeModelsAnon ¶
ProbeModelsAnon handles POST /api/v1/probe-models. No credential ID needed — caller passes apiKey + baseURL directly. Auth is still required so arbitrary API keys can't be proxied by unauthenticated users. The baseURL is validated against SSRF rules before making any outbound request.
Types ¶
type AdminProviderCredentialsHandler ¶
type AdminProviderCredentialsHandler struct {
// contains filtered or unexported fields
}
AdminProviderCredentialsHandler handles CRUD for admin provider credentials.
func NewAdminProviderCredentialsHandler ¶
func NewAdminProviderCredentialsHandler(store CredentialStore, provider secrets.RootKeyProvider) *AdminProviderCredentialsHandler
NewAdminProviderCredentialsHandler creates a new handler.
func (*AdminProviderCredentialsHandler) Create ¶
func (h *AdminProviderCredentialsHandler) Create(c *gin.Context)
Create handles POST /api/v1/admin/provider-credentials.
func (*AdminProviderCredentialsHandler) CreateAutoApply ¶
func (h *AdminProviderCredentialsHandler) CreateAutoApply(c *gin.Context)
CreateAutoApply handles POST /api/v1/admin/provider-credentials/:id/auto-apply.
func (*AdminProviderCredentialsHandler) Delete ¶
func (h *AdminProviderCredentialsHandler) Delete(c *gin.Context)
Delete handles DELETE /api/v1/admin/provider-credentials/:id.
func (*AdminProviderCredentialsHandler) DeleteAutoApply ¶
func (h *AdminProviderCredentialsHandler) DeleteAutoApply(c *gin.Context)
DeleteAutoApply handles DELETE /api/v1/admin/provider-credentials/:id/auto-apply/:targetType/:targetId.
func (*AdminProviderCredentialsHandler) Get ¶
func (h *AdminProviderCredentialsHandler) Get(c *gin.Context)
Get handles GET /api/v1/admin/provider-credentials/:id.
func (*AdminProviderCredentialsHandler) List ¶
func (h *AdminProviderCredentialsHandler) List(c *gin.Context)
List handles GET /api/v1/admin/provider-credentials.
func (*AdminProviderCredentialsHandler) ListAutoApply ¶
func (h *AdminProviderCredentialsHandler) ListAutoApply(c *gin.Context)
ListAutoApply handles GET /api/v1/admin/provider-credentials/:id/auto-apply.
func (*AdminProviderCredentialsHandler) ProbeModels ¶
func (h *AdminProviderCredentialsHandler) ProbeModels(c *gin.Context)
ProbeModels handles GET /api/v1/admin/provider-credentials/:id/models. Admin variant — uses the platform KEK to decrypt.
func (*AdminProviderCredentialsHandler) SetAutoApplyStore ¶
func (h *AdminProviderCredentialsHandler) SetAutoApplyStore(s AutoApplyStore)
SetAutoApplyStore sets the auto-apply store (called after construction).
func (*AdminProviderCredentialsHandler) Update ¶
func (h *AdminProviderCredentialsHandler) Update(c *gin.Context)
Update handles PUT /api/v1/admin/provider-credentials/:id.
type AdminSessionHandler ¶
type AdminSessionHandler struct {
// contains filtered or unexported fields
}
AdminSessionHandler serves admin-only session recovery endpoints.
ForceAbortSession clears a workspace-scoped session that is stuck in the ProxyHandler's active-session set (wsstate.Store.activeSess) after the workspace pod has been deleted or become unreachable. It does NOT call the opencode proxy — the pod may be gone, by design. It mirrors the local cleanup half of ProxyHandler.DeleteSession (proxy_handlers.go:234-268) minus the proxy call, plus an audit-log row.
The session-index DB row is deliberately NOT cleaned: force-abort clears only the stuck active-session marker, not the session itself. The session may still be live in opencode (just not busy). DeleteWorkspace already does not clean session_index rows (pre-existing pattern).
Audit-log failure is non-fatal: the force-abort succeeds even if the DB INSERT fails, because incident recovery must not be blocked by a DB hiccup (differs from AdminDiscardDLQ which returns 500 on audit failure — that operation is not incident-recovery-critical).
func NewAdminSessionHandler ¶
func NewAdminSessionHandler(proxy *ProxyHandler, db *sql.DB, logger pkginterfaces.LoggerInterface) *AdminSessionHandler
func (*AdminSessionHandler) ForceAbortSession ¶
func (h *AdminSessionHandler) ForceAbortSession(c *gin.Context)
type AgentReloadHandler ¶
type AgentReloadHandler struct {
// contains filtered or unexported fields
}
AgentReloadHandler handles POST /api/v1/workspaces/:id/agent/reload.
func NewAgentReloadHandler ¶
func NewAgentReloadHandler( wsSvc WorkspaceServicer, db AgentStateStore, podResolver PodIPResolver, httpClient *http.Client, logger pkginterfaces.LoggerInterface, ) *AgentReloadHandler
NewAgentReloadHandler constructs the handler with all dependencies.
func (*AgentReloadHandler) Reload ¶
func (h *AgentReloadHandler) Reload(c *gin.Context)
Reload handles POST /api/v1/workspaces/:id/agent/reload.
func (*AgentReloadHandler) SetBrokerPublisher ¶
func (h *AgentReloadHandler) SetBrokerPublisher(b BrokerPublisher)
SetBrokerPublisher injects the SSE broker for publishing dismissed events on dispose.
func (*AgentReloadHandler) SetMetrics ¶
func (h *AgentReloadHandler) SetMetrics(m MetricsRecorder)
SetMetrics injects the metrics recorder.
func (*AgentReloadHandler) SetPasswordGetter ¶
func (h *AgentReloadHandler) SetPasswordGetter(provider interfaces.WorkspacePasswordProvider)
SetPasswordGetter injects the password getter for drain mode (needs opencode client).
func (*AgentReloadHandler) SetQueueClearer ¶
func (h *AgentReloadHandler) SetQueueClearer(q QueueClearer)
SetQueueClearer injects the queue service for clearing queued messages on dispose.
func (*AgentReloadHandler) SetSSETracker ¶
func (h *AgentReloadHandler) SetSSETracker(t *sse.Tracker)
SetSSETracker injects the tracker for drain mode support.
type AgentRoleHandler ¶
type AgentRoleHandler struct {
// contains filtered or unexported fields
}
AgentRoleHandler handles platform and org agent role CRUD.
func NewAgentRoleHandler ¶
func NewAgentRoleHandler(store roleStore, svc *role.Service, authSvc orgAuthService, logger policyLogger) *AgentRoleHandler
func (*AgentRoleHandler) ClearWorkspaceRole ¶
func (h *AgentRoleHandler) ClearWorkspaceRole(c *gin.Context)
ClearWorkspaceRole removes the workspace's role assignment so it falls back to the platform default. Enforces the same allow_user_prompt gate as SetWorkspaceRole.
func (*AgentRoleHandler) CreateOrg ¶
func (h *AgentRoleHandler) CreateOrg(c *gin.Context)
func (*AgentRoleHandler) CreatePlatform ¶
func (h *AgentRoleHandler) CreatePlatform(c *gin.Context)
func (*AgentRoleHandler) DeleteOrg ¶
func (h *AgentRoleHandler) DeleteOrg(c *gin.Context)
func (*AgentRoleHandler) DeletePlatform ¶
func (h *AgentRoleHandler) DeletePlatform(c *gin.Context)
func (*AgentRoleHandler) GetEffectiveWorkspaceRole ¶
func (h *AgentRoleHandler) GetEffectiveWorkspaceRole(c *gin.Context)
func (*AgentRoleHandler) GetOrg ¶
func (h *AgentRoleHandler) GetOrg(c *gin.Context)
func (*AgentRoleHandler) GetPlatform ¶
func (h *AgentRoleHandler) GetPlatform(c *gin.Context)
func (*AgentRoleHandler) GetWorkspaceRole ¶
func (h *AgentRoleHandler) GetWorkspaceRole(c *gin.Context)
func (*AgentRoleHandler) ListOrg ¶
func (h *AgentRoleHandler) ListOrg(c *gin.Context)
func (*AgentRoleHandler) ListPlatform ¶
func (h *AgentRoleHandler) ListPlatform(c *gin.Context)
func (*AgentRoleHandler) SetWorkspaceRole ¶
func (h *AgentRoleHandler) SetWorkspaceRole(c *gin.Context)
func (*AgentRoleHandler) UpdateOrg ¶
func (h *AgentRoleHandler) UpdateOrg(c *gin.Context)
func (*AgentRoleHandler) UpdatePlatform ¶
func (h *AgentRoleHandler) UpdatePlatform(c *gin.Context)
type AgentStateChecker ¶
type AgentStateChecker interface {
GetLastCredentialChangedAt(ctx context.Context, workspaceID string) (time.Time, error)
}
AgentStateChecker is the interface for checking workspace agent state.
type AgentStateStore ¶
type AgentStateStore interface {
GetLastCredentialChangedAt(ctx context.Context, workspaceID string) (time.Time, error)
MarkAgentReloaded(ctx context.Context, tx *sql.Tx, workspaceID string, priorChangedAt time.Time) (time.Time, error)
BeginTx(ctx context.Context, opts *sql.TxOptions) (*sql.Tx, error)
}
AgentStateStore is the DB surface needed by the reload handler.
type AuditHandler ¶
type AuditHandler struct {
// contains filtered or unexported fields
}
AuditHandler handles GET /api/v1/orgs/:id/audit (admin-only).
func NewAuditHandler ¶
func NewAuditHandler(store auditStore) *AuditHandler
NewAuditHandler constructs the handler.
func (*AuditHandler) List ¶
func (h *AuditHandler) List(c *gin.Context)
List handles GET /api/v1/orgs/:id/audit.
func (*AuditHandler) ListCrossOrg ¶
func (h *AuditHandler) ListCrossOrg(c *gin.Context)
ListCrossOrg handles GET /api/v1/admin/audit (platform-admin only). It reads the org_id / actor_id / domain / limit / offset query params, constructs an AuditFilters, and returns all matching audit entries across every org.
type AutoApplyStore ¶
type AutoApplyStore interface {
CreateAutoApply(ctx context.Context, credentialID, targetType string, targetID *string, priority int) error
DeleteAutoApply(ctx context.Context, credentialID, targetType string, targetID *string) error
ListAutoApply(ctx context.Context, credentialID string) ([]secrets.AutoApplyRule, error)
}
AutoApplyStore abstracts auto-apply DB operations.
type BrokerPublisher ¶
type BrokerPublisher interface {
PublishToWorkspace(workspaceID string, event apitypes.WorkspaceSSEEvent)
}
BrokerPublisher is the minimal SSE broker interface needed by the reload handler.
type BulkReloadHandler ¶
type BulkReloadHandler struct {
// contains filtered or unexported fields
}
BulkReloadHandler handles POST /api/v1/users/me/agents/reload.
func NewBulkReloadHandler ¶
func NewBulkReloadHandler( pendingLister PendingReloadLister, wsSvc WorkspaceServicer, db AgentStateStore, podResolver PodIPResolver, httpClient *http.Client, logger pkginterfaces.LoggerInterface, ) *BulkReloadHandler
NewBulkReloadHandler constructs the bulk reload handler.
func (*BulkReloadHandler) BulkReload ¶
func (h *BulkReloadHandler) BulkReload(c *gin.Context)
BulkReload streams per-workspace reload results as NDJSON.
func (*BulkReloadHandler) SetBrokerPublisher ¶
func (h *BulkReloadHandler) SetBrokerPublisher(b BrokerPublisher)
SetBrokerPublisher injects the SSE broker for publishing dismissed events on dispose.
func (*BulkReloadHandler) SetMetrics ¶
func (h *BulkReloadHandler) SetMetrics(m MetricsRecorder)
SetMetrics injects the metrics recorder.
func (*BulkReloadHandler) SetPasswordGetter ¶
func (h *BulkReloadHandler) SetPasswordGetter(provider interfaces.WorkspacePasswordProvider)
SetPasswordGetter injects the password getter for drain mode.
func (*BulkReloadHandler) SetQueueClearer ¶
func (h *BulkReloadHandler) SetQueueClearer(q QueueClearer)
SetQueueClearer injects the queue service for clearing queued messages on dispose.
func (*BulkReloadHandler) SetSSETracker ¶
func (h *BulkReloadHandler) SetSSETracker(t *sse.Tracker)
SetSSETracker injects the SSE tracker for drain mode.
type Catalog ¶
type Catalog struct {
Connected []string `json:"connected"`
Providers []CatalogProvider `json:"all"`
}
Catalog is the normalized, agent-agnostic shape of a model listing. Connected holds provider IDs the agent reports as active; Providers holds the full provider→model tree. A ModelCatalogParser converts an agent-specific wire format into this shape.
type CatalogModel ¶
type CatalogModel struct {
ID string `json:"id"`
Name string `json:"name"`
Cost ProviderModelCost `json:"cost"`
}
CatalogModel is a single model entry in a parsed agent catalog.
type CatalogProvider ¶
type CatalogProvider struct {
ID string `json:"id"`
Models map[string]CatalogModel `json:"models"`
}
CatalogProvider groups models under a provider. Models is keyed by the provider-local model key (which may differ from the model's own ID).
type CredentialBindingStore ¶
type CredentialBindingStore interface {
BindCredentialToWorkspace(ctx context.Context, credentialID, workspaceID string) error
// UnbindCredentialFromWorkspace removes an EXPLICIT binding.
// Returns secrets.ErrAutoBindingProtected for auto-managed bindings.
UnbindCredentialFromWorkspace(ctx context.Context, credentialID, workspaceID string) error
// GetCredentialBindingsWithSource returns bindings with source type (explicit vs auto).
GetCredentialBindingsWithSource(ctx context.Context, credentialID, userID string) ([]secrets.CredentialBindingInfo, error)
// GetCredentialBindings returns workspace IDs bound to the credential.
GetCredentialBindings(ctx context.Context, credentialID, userID string) ([]string, error)
// BindCredentialToAllUserWorkspaces binds a credential to every workspace owned by userID.
BindCredentialToAllUserWorkspaces(ctx context.Context, credentialID, userID string) error
}
CredentialBindingStore abstracts user-only credential↔workspace binding operations (the cross-entity concern that only the user handler needs).
type CredentialResponse ¶
type CredentialResponse struct {
ID string `json:"id"`
OrgID string `json:"orgId,omitempty"`
Name string `json:"name"`
Kind string `json:"kind"`
Slug string `json:"slug"`
BaseURL string `json:"baseURL,omitempty"`
ModelAllowlist []string `json:"modelAllowlist"`
ModelContextLimits map[string]int `json:"modelContextLimits"`
ModelOutputLimits map[string]int `json:"modelOutputLimits"`
CreatedAt string `json:"createdAt"`
UpdatedAt string `json:"updatedAt"`
BindWarning string `json:"bindWarning,omitempty"`
}
CredentialResponse is the API response for any provider credential. Never exposes apiKey. BaseURL is extracted from the encrypted ciphertext. OrgID is populated only for org-scoped credentials (omitted otherwise). BindWarning is set only by org Create when auto-bind fails (non-fatal).
Epic 55:
- Kind: SDK-class enum (openai, anthropic, openai_compatible, ...).
- Slug: per-owner unique identity; reaches opencode as providerID in agent-config.json.
- Name: free-form display label.
type CredentialStateWriter ¶
type CredentialStateWriter interface {
MarkCredentialChanged(ctx context.Context, workspaceID string) error
}
CredentialStateWriter records that workspace credentials have changed. Satisfied by *database.Service.
type CredentialStore ¶
type CredentialStore interface {
CreateCredential(ctx context.Context, ownerType, ownerID string, row *secrets.CredentialRow) error
ListCredentials(ctx context.Context, ownerType, ownerID string) ([]*secrets.CredentialRow, error)
GetCredential(ctx context.Context, ownerType, ownerID, credID string) (*secrets.CredentialRow, error)
UpdateCredential(ctx context.Context, ownerType, ownerID, credID string, row *secrets.CredentialRow) error
DeleteCredential(ctx context.Context, ownerType, ownerID, credID string) error
}
CredentialStore is the unified DB interface for provider credential CRUD, scoped by (ownerType, ownerID) for multi-tenant isolation. All three credential handlers (admin/user/org) depend on it; their specialized stores (bindings, auto-apply) are wired separately.
type DEKUnlocker ¶
type DEKUnlocker interface {
UnlockDEKWithSigningKey(ctx context.Context, userID string, password []byte, sessionID string, ttl time.Duration, activeSigningKey []byte) error
}
DEKUnlocker is the caller-shaped subset of KeyService used by the soft-unlock endpoint. *secrets.KeyService satisfies it.
Returns a non-nil error only when the unlock could not be completed (wrong password, DB issue, no user keys). The durable jwt_sessions write inside UnlockDEKWithSigningKey is best-effort and surfaces via logs, not the return value — login-style behavior.
type EmailHandler ¶
type EmailHandler struct {
// contains filtered or unexported fields
}
EmailHandler exposes admin-only email operations (test-send). It wraps the EmailService which resolves the configured provider (SES/Noop).
func NewEmailHandler ¶
func NewEmailHandler(svc *emailsvc.Service, rl rateCounter, log emailLogger) *EmailHandler
NewEmailHandler constructs an EmailHandler. svc must be non-nil. rl may be nil — when nil, the per-endpoint test-send rate limit is skipped (the global RateLimitMiddleware still applies); this is intended for tests and for deployments that prefer to rely solely on the global limiter. log may be nil in tests; when nil, send-failure errors are not logged (the mapped category is still returned to the caller).
func (*EmailHandler) TestSend ¶
func (h *EmailHandler) TestSend(c *gin.Context)
TestSend handles POST /api/v1/admin/email/test.
Sends a test email to the given address so an admin can verify the SES wiring end-to-end. The endpoint is admin-only (wired behind AdminGuard in the router) and rate-limited to testSendRateLimit per admin per hour.
Response contract:
- SES success: 200 { "sent": true, "provider": "ses" }
- Noop mode: 200 { "sent": false, "provider": "noop" }
- Rate limited: 429 { "error": "rate limit exceeded", "limit": 5 }
- Send failure: 502 { "error": "<mapped category>" }
Noop mode reports sent=false so the admin knows no real email was delivered even though the call "succeeded" (NoopProvider logs to stderr and returns nil).
type EmailVerifierAdapter ¶
type EmailVerifierAdapter struct {
// contains filtered or unexported fields
}
EmailVerifierAdapter implements auth.EmailVerifier by creating a token, storing the hash, and sending the verification email via EmailService. Wired in app.go and passed to auth.Service.SetEmailVerifier.
func NewEmailVerifierAdapter ¶
func NewEmailVerifierAdapter(store emailTokenStore, email *emailsvc.Service, baseURL string) *EmailVerifierAdapter
NewEmailVerifierAdapter constructs the adapter.
func (*EmailVerifierAdapter) SendVerification ¶
func (a *EmailVerifierAdapter) SendVerification(ctx context.Context, userID, emailAddr string) error
SendVerification creates a single-use email-verify token, stores the hash, and sends the verification link via the EmailService.
type EmailVerifyHandler ¶
type EmailVerifyHandler struct {
// contains filtered or unexported fields
}
EmailVerifyHandler handles the email-verification flow: verify a token and resend the verification email.
func NewEmailVerifyHandler ¶
func NewEmailVerifyHandler( store emailTokenStore, users emailVerifyUserLookup, email *emailsvc.Service, resender emailResender, log passwordResetLogger, ) *EmailVerifyHandler
NewEmailVerifyHandler constructs the handler.
func (*EmailVerifyHandler) Resend ¶
func (h *EmailVerifyHandler) Resend(c *gin.Context)
Resend handles POST /api/v1/auth/verify-email/resend.
Public, returns 202 always (no enumeration). Looks up the user by email; if found and unverified, sends a new verification link.
func (*EmailVerifyHandler) Verify ¶
func (h *EmailVerifyHandler) Verify(c *gin.Context)
Verify handles POST /api/v1/auth/verify-email.
Public (the token IS the credential). Verifies the hash, checks expiry + consumption + kind, sets email_verified=true.
type ErrDrainTimeout ¶
type ErrDrainTimeout struct {
BusySessions []string
}
ErrDrainTimeout is returned by WaitUntilIdle when the deadline elapses before all sessions become idle.
func (*ErrDrainTimeout) Error ¶
func (e *ErrDrainTimeout) Error() string
type InternalOrgStatusHandler ¶
type InternalOrgStatusHandler struct {
// contains filtered or unexported fields
}
InternalOrgStatusHandler serves GET /api/v1/internal/orgs/:orgID/status — the cluster-internal endpoint the workspace controller polls (with a 30s cache) to drive org-suspension of workspaces (D20, US-43.19).
This endpoint is intentionally NOT behind AuthMiddleware: the controller has no user identity. The PRIMARY boundary is a mandatory shared-secret header (X-Internal-Token) read from LLMSAFESPACES_INTERNAL_TOKEN (F5, US-43.19). When the env var is unset the endpoint FAILS CLOSED with 403: serving it unauthenticated would let any pod that can route to the API enumerate which orgs are suspended. The chart sets the token on BOTH the API and the controller so a single mounted Secret configures both sides. The comparison is constant-time to avoid a timing leak of the shared secret. It MUST only ever return org status, never secrets.
func NewInternalOrgStatusHandler ¶
func NewInternalOrgStatusHandler(store internalOrgStatusReader) *InternalOrgStatusHandler
NewInternalOrgStatusHandler constructs the handler.
func (*InternalOrgStatusHandler) GetOrgStatus ¶
func (h *InternalOrgStatusHandler) GetOrgStatus(c *gin.Context)
GetOrgStatus handles GET /api/v1/internal/orgs/:orgID/status.
Fail-safe: a missing org row (hard-deleted or never existed) returns {"status":"active"} so the controller does NOT suspend the workspace. Per D20, an unwarranted suspension (deleting a running pod) is more disruptive than leaving it active during an anomaly.
type InvitationsHandler ¶
type InvitationsHandler struct {
// contains filtered or unexported fields
}
InvitationsHandler handles org invitation CRUD and the accept/decline flows.
func NewInvitationsHandler ¶
func NewInvitationsHandler(store invitationStore, mailer email.EmailProvider, authSvc orgAuthService, baseURL string, logger invitationLogger) *InvitationsHandler
NewInvitationsHandler constructs the handler. email may be nil in which case Create/Resend succeed but no email is sent (dev mode). logger may be nil.
func (*InvitationsHandler) Accept ¶
func (h *InvitationsHandler) Accept(c *gin.Context)
Accept handles POST /api/v1/invitations/:token/accept (JWT required).
func (*InvitationsHandler) Create ¶
func (h *InvitationsHandler) Create(c *gin.Context)
Create handles POST /api/v1/orgs/:id/invitations.
func (*InvitationsHandler) Decline ¶
func (h *InvitationsHandler) Decline(c *gin.Context)
Decline handles POST /api/v1/invitations/:token/decline (JWT required).
func (*InvitationsHandler) Delete ¶
func (h *InvitationsHandler) Delete(c *gin.Context)
Delete handles DELETE /api/v1/orgs/:id/invitations/:invID.
func (*InvitationsHandler) GetByToken ¶
func (h *InvitationsHandler) GetByToken(c *gin.Context)
GetByToken handles GET /api/v1/invitations/:token (public — no auth).
func (*InvitationsHandler) List ¶
func (h *InvitationsHandler) List(c *gin.Context)
List handles GET /api/v1/orgs/:id/invitations.
func (*InvitationsHandler) Resend ¶
func (h *InvitationsHandler) Resend(c *gin.Context)
Resend handles POST /api/v1/orgs/:id/invitations/:invID/resend. Generates a new token (invalidating the old one), resets the expiry, and re-sends.
func (*InvitationsHandler) SetCredentialBinder ¶
func (h *InvitationsHandler) SetCredentialBinder(b orgCredentialBinder)
SetCredentialBinder wires the org credential binder used after invitation acceptance (F7). Optional — nil means no credential seeding on join.
func (*InvitationsHandler) VerifyUserForInvitation ¶
func (h *InvitationsHandler) VerifyUserForInvitation(c *gin.Context)
VerifyUserForInvitation handles POST /api/v1/orgs/:id/invitations/:invID/verify-user.
Org-admin only (registered under orgAdminGroup). The "member force-verify" surface added in PR #343 only acted on already-accepted members; this handler closes the gap for *pending* invitations: an admin can flip the invitee's users.email_verified=true so the invitee (who already has an account but never completed email verification) can log in. The invitation row stays pending — the user must still click the invitation link to accept and join the org.
Behavior:
- Invitation exists, belongs to this org, and is still pending → look up users.id by inv.email; if found, MarkUserEmailVerified(userID); audit.
- User does NOT exist → 422 {"error":"no_account_for_email"}. The frontend uses the machine-parseable code to render a clear "user must sign up first" message rather than treating it as a transient error.
- Cross-org invitation → 404 (do not leak invitation existence across orgs).
- Already accepted/declined → 409 (use member.verify on the resulting member).
- Expired → 410 (matches Accept's behavior).
Idempotent at the DB level. The audit event records the admin's intent regardless of whether the user was previously verified.
type KeyRotator ¶
type KeyRotator interface {
RotateKeyWithPassword(ctx context.Context, userID string, password []byte, sessionID string, ttl time.Duration) (secrets.RotationResult, error)
ChangePassword(ctx context.Context, userID, sessionID string, oldPassword, newPassword []byte) error
ResetWithRecoveryKey(ctx context.Context, userID string, recoveryKeyHex string, newPassword []byte) (string, error)
}
KeyRotator is the interface needed by the rotation handler.
type LoginDiscoveryHandler ¶
type LoginDiscoveryHandler struct {
// contains filtered or unexported fields
}
LoginDiscoveryHandler implements POST /api/v1/auth/lookup — the email-led login discovery endpoint (Epic 54, US-54.1).
The endpoint resolves an email to a single redirect URL pointing at the user's org (subdomain if routing is enabled, direct SSO start URL otherwise). The response shape is uniform across all non-validation branches:
200 OK
{ "redirectUrl": "<url>" }
Found users with an org get a real redirect; everyone else (not found, no org, suspended, DB error) gets the same 200 with a not-found redirect URL. This matches the password_reset.go:119 enumeration-safe precedent exactly: uniform status, uniform body shape, no timing pad.
func NewLoginDiscoveryHandler ¶
func NewLoginDiscoveryHandler( users loginDiscoveryUserLookup, orgs loginDiscoveryOrgLookup, baseDomain string, log loginDiscoveryLogger, ) *LoginDiscoveryHandler
NewLoginDiscoveryHandler constructs the handler. baseDomain is the subdomain base (e.g. "app.example.com"); when empty, the handler falls back to the direct SSO start URL (/api/v1/auth/sso/<slug>/start) which works regardless of chart config.
func (*LoginDiscoveryHandler) Lookup ¶
func (h *LoginDiscoveryHandler) Lookup(c *gin.Context)
Lookup handles POST /api/v1/auth/lookup.
Enumeration-safe: always returns 200 with { redirectUrl } on valid input. DB errors are logged and masked — never surfaced as 5xx.
type MetricsRecorder ¶
type MetricsRecorder interface {
RecordAgentReload(result string, durationMs int64, drained bool)
RecordAgentReloadDrainTimeout(elapsedMs int64)
RecordAgentReloadBulk(total, succeeded, failed int)
}
MetricsRecorder is the minimal metrics interface for reload handlers.
type ModelAvailability ¶
type ModelAvailability string
const ( ModelAvailable ModelAvailability = "available" ModelFreeTier ModelAvailability = "free" )
type ModelCache ¶
type ModelCache interface {
Get(workspaceID string) []byte
Set(workspaceID string, data []byte)
Evict(workspaceID string)
Keys() []string
}
ModelCache abstracts model catalog caching. Default: in-process map.
func NewInMemoryModelCache ¶
func NewInMemoryModelCache() ModelCache
type ModelCatalogParser ¶
ModelCatalogParser decodes an agent's model catalog response into the normalized Catalog shape. The concrete implementation is selected at construction time based on the agent type/version — callers never branch on the wire format.
Worklog 0377 H1-a′: the interface exists so that opencode schema drift across versions (and, if a future runtime runs a different agent, a different wire format) is handled by adding a parser variant rather than branching in every caller. One implementation exists today (opencodeProviderParser); the interface earns its keep because the opencode /provider schema is documented to vary by version.
func NewOpencodeProviderParser ¶
func NewOpencodeProviderParser() ModelCatalogParser
NewOpencodeProviderParser returns a parser for opencode's /provider format.
type ModelClient ¶
type ModelClient interface {
ListModels(ctx context.Context, userID, workspaceID string) ([]byte, error)
PatchConfig(ctx context.Context, userID, workspaceID string, config map[string]any) error
}
ModelClient is the caller-shaped interface ModelsHandler needs from an agent client: fetch the catalog and push config changes. Worklog 0377 H2-a: split from the fat AgentClient (5 methods) so the handler depends on exactly the 2 methods it uses, and fakes only need to stub 2.
type ModelSelectionRecorder ¶
type ModelSelectionRecorder interface {
RecordModelSelection(modelID, providerID string)
}
ModelSelectionRecorder records model selection events for billing/metering.
type ModelSelectionRequest ¶
type ModelSelectionRequest struct {
Model string `json:"model" binding:"required"`
}
ModelSelectionRequest is the request body for PUT /workspaces/:id/model.
type ModelStore ¶
type ModelStore interface {
UpdateWorkspace(ctx context.Context, workspaceID string, updates types.WorkspaceUpdates) error
GetDefaultModel(ctx context.Context, workspaceID string) (string, error)
GetWorkspace(ctx context.Context, workspaceID string) (*types.WorkspaceMetadata, error)
}
ModelStore provides all database operations needed by model endpoints. Satisfied by *database.Service.
type ModelsHandler ¶
type ModelsHandler struct {
// contains filtered or unexported fields
}
ModelsHandler handles GET /workspaces/:id/models and PUT /workspaces/:id/model (US-29.5). Extracted from SecretsHandler to enforce single responsibility. Consumes ModelClient (H2-a) for opencode HTTP communication and ModelCatalogParser (H1-a′) to decode the catalog response into a typed Catalog.
func NewModelsHandler ¶
func NewModelsHandler(agentClient ModelClient) *ModelsHandler
NewModelsHandler creates a ModelsHandler with the required ModelClient. The parser defaults to opencodeProviderParser; override via SetCatalogParser for tests or a future agent variant. Optional deps via the Set methods.
func (*ModelsHandler) ListModels ¶
func (h *ModelsHandler) ListModels(c *gin.Context)
ListModels handles GET /api/v1/workspaces/:id/models.
func (*ModelsHandler) SetAgentClient ¶
func (h *ModelsHandler) SetAgentClient(ac ModelClient)
func (*ModelsHandler) SetCatalogParser ¶
func (h *ModelsHandler) SetCatalogParser(p ModelCatalogParser)
func (*ModelsHandler) SetLogger ¶
func (h *ModelsHandler) SetLogger(l pkginterfaces.LoggerInterface)
func (*ModelsHandler) SetMetricsRecorder ¶
func (h *ModelsHandler) SetMetricsRecorder(r ModelSelectionRecorder)
func (*ModelsHandler) SetModel ¶
func (h *ModelsHandler) SetModel(c *gin.Context)
SetModel handles PUT /api/v1/workspaces/:id/model.
func (*ModelsHandler) SetModelCache ¶
func (h *ModelsHandler) SetModelCache(c ModelCache)
func (*ModelsHandler) SetModelStore ¶
func (h *ModelsHandler) SetModelStore(s ModelStore)
func (*ModelsHandler) SetPolicyChecker ¶
func (h *ModelsHandler) SetPolicyChecker(p OrgPolicyChecker)
func (*ModelsHandler) SetRelayActive ¶
func (h *ModelsHandler) SetRelayActive(active bool)
func (*ModelsHandler) SetRelayChecker ¶
func (h *ModelsHandler) SetRelayChecker(rc RelayStateChecker)
type OrgBilling ¶
type OrgBilling interface {
CreateCheckoutSession(ctx context.Context, customerID, planID, successURL, cancelURL string) (string, error)
CreatePortalSession(ctx context.Context, customerID, returnURL string) (string, error)
}
OrgBilling creates Checkout/Portal sessions for org subscription management (plan upgrades, billing portal access). Customer creation was removed with the self-service org-creation flow (design 0031 D1).
func NewOrgBilling ¶
func NewOrgBilling(p billing.CheckoutProvider) OrgBilling
NewOrgBilling adapts a billing.CheckoutProvider into an OrgBilling.
type OrgCredentialsHandler ¶
type OrgCredentialsHandler struct {
// contains filtered or unexported fields
}
OrgCredentialsHandler handles org credential endpoints.
func NewOrgCredentialsHandler ¶
func NewOrgCredentialsHandler(store CredentialStore, orgOps orgBindingAndAutoApplyStore, provider secrets.RootKeyProvider, authSvc orgAuthService) *OrgCredentialsHandler
NewOrgCredentialsHandler creates a new OrgCredentialsHandler.
func (*OrgCredentialsHandler) Create ¶
func (h *OrgCredentialsHandler) Create(c *gin.Context)
Create handles POST /api/v1/orgs/:id/credentials.
func (*OrgCredentialsHandler) CreateAutoApply ¶
func (h *OrgCredentialsHandler) CreateAutoApply(c *gin.Context)
CreateAutoApply handles POST /api/v1/orgs/:id/credentials/:credID/auto-apply.
func (*OrgCredentialsHandler) Delete ¶
func (h *OrgCredentialsHandler) Delete(c *gin.Context)
Delete handles DELETE /api/v1/orgs/:id/credentials/:credID.
func (*OrgCredentialsHandler) DeleteAutoApply ¶
func (h *OrgCredentialsHandler) DeleteAutoApply(c *gin.Context)
DeleteAutoApply handles DELETE /api/v1/orgs/:id/credentials/:credID/auto-apply.
func (*OrgCredentialsHandler) List ¶
func (h *OrgCredentialsHandler) List(c *gin.Context)
List handles GET /api/v1/orgs/:id/credentials.
func (*OrgCredentialsHandler) ListAutoApply ¶
func (h *OrgCredentialsHandler) ListAutoApply(c *gin.Context)
ListAutoApply handles GET /api/v1/orgs/:id/credentials/:credID/auto-apply.
func (*OrgCredentialsHandler) ProbeModels ¶
func (h *OrgCredentialsHandler) ProbeModels(c *gin.Context)
ProbeModels handles GET /api/v1/orgs/:id/credentials/:credID/models. It decrypts the stored credential and calls the provider's /v1/models (OpenAI-compatible) to discover available model IDs, merged with any saved context limits so the UI can pre-populate the config table.
func (*OrgCredentialsHandler) Update ¶
func (h *OrgCredentialsHandler) Update(c *gin.Context)
Update handles PUT /api/v1/orgs/:id/credentials/:credID.
type OrgPolicyChecker ¶
type OrgPolicyChecker interface {
GetEffectivePolicy(ctx context.Context, orgID string) (*types.OrgPolicyValues, error)
}
OrgPolicyChecker is the minimal interface needed to filter models by org policy. The policy.Service implements it.
type OrgsHandler ¶
type OrgsHandler struct {
// contains filtered or unexported fields
}
OrgsHandler handles org CRUD endpoints.
func NewOrgsHandler ¶
func NewOrgsHandler( store orgStore, authSvc orgAuthService, ) *OrgsHandler
NewOrgsHandler creates a new OrgsHandler.
func (*OrgsHandler) AddMember ¶
func (h *OrgsHandler) AddMember(c *gin.Context)
AddMember handles POST /api/v1/orgs/:id/members.
func (*OrgsHandler) ChangeMemberRole ¶
func (h *OrgsHandler) ChangeMemberRole(c *gin.Context)
ChangeMemberRole handles PUT /api/v1/orgs/:id/members/:userID.
func (*OrgsHandler) Checkout ¶
func (h *OrgsHandler) Checkout(c *gin.Context)
Checkout handles POST /api/v1/orgs/:id/billing/checkout. Creates a Stripe Checkout Session for the requested plan and returns its hosted URL.
func (*OrgsHandler) Create ¶
func (h *OrgsHandler) Create(c *gin.Context)
Create handles POST /api/v1/orgs.
Per design 0031 D1, org creation is platform-admin only. Non-admin callers receive 403. A platform admin supplies the intended owner's email; the backend resolves it to a user ID (single lookup, 404 when no such user) and creates the org already active with the requested plan (default enterprise), adding the resolved user as the org's first admin member. The caller admin is recorded as CreatedBy; the owner is the email-resolved user. No Stripe Checkout session is created at org-creation time — the self-service flow is deferred to the future billing-portal epic.
func (*OrgsHandler) Delete ¶
func (h *OrgsHandler) Delete(c *gin.Context)
Delete handles DELETE /api/v1/orgs/:id.
func (*OrgsHandler) Get ¶
func (h *OrgsHandler) Get(c *gin.Context)
Get handles GET /api/v1/orgs/:id.
func (*OrgsHandler) GetOrg ¶
func (h *OrgsHandler) GetOrg(ctx context.Context, orgID string) (*types.Organization, error)
GetOrg exposes orgStore.GetOrg so middleware.FeatureGuard can read the org's plan without depending on the store directly. Satisfies orgPlanReader.
func (*OrgsHandler) IsOrgAdmin ¶
IsOrgAdmin satisfies the middleware.orgMemberChecker interface by delegating to orgStore.
func (*OrgsHandler) IsOrgMember ¶
IsOrgMember satisfies the middleware.orgMemberChecker interface by delegating to orgStore.
func (*OrgsHandler) List ¶
func (h *OrgsHandler) List(c *gin.Context)
List handles GET /api/v1/orgs.
func (*OrgsHandler) ListMembers ¶
func (h *OrgsHandler) ListMembers(c *gin.Context)
ListMembers handles GET /api/v1/orgs/:id/members.
func (*OrgsHandler) ListWorkspaces ¶
func (h *OrgsHandler) ListWorkspaces(c *gin.Context)
ListWorkspaces handles GET /api/v1/orgs/:id/workspaces.
func (*OrgsHandler) Portal ¶
func (h *OrgsHandler) Portal(c *gin.Context)
Portal handles POST /api/v1/orgs/:id/billing/portal. Creates a Stripe Customer Portal Session and returns its URL.
func (*OrgsHandler) RemoveMember ¶
func (h *OrgsHandler) RemoveMember(c *gin.Context)
RemoveMember handles DELETE /api/v1/orgs/:id/members/:userID.
func (*OrgsHandler) SetBilling ¶
func (h *OrgsHandler) SetBilling(b OrgBilling, successURL, cancelURL, portalURL string)
SetBilling wires the Stripe checkout/portal provider and redirect URLs. When not called (or passed a nil provider), org creation succeeds but produces no checkout URL — development mode without Stripe configured.
func (*OrgsHandler) SetLogger ¶
func (h *OrgsHandler) SetLogger(l orgsLogger)
SetLogger wires an optional logger used to surface non-fatal audit emission failures (e.g. a VerifyMember action that succeeds but whose audit row could not be written). When not called, audit failures are silent.
func (*OrgsHandler) Update ¶
func (h *OrgsHandler) Update(c *gin.Context)
Update handles PUT /api/v1/orgs/:id.
func (*OrgsHandler) VerifyMember ¶
func (h *OrgsHandler) VerifyMember(c *gin.Context)
VerifyMember handles POST /api/v1/orgs/:id/members/:userID/verify.
Org-admin only (OrgAdminGuard middleware). Marks the member's user account as email_verified=true, bypassing the email-verification token flow. Use cases: the admin has confirmed the member's identity out-of-band (e.g. in person, via a trusted channel), or the member cannot receive the verification email and the admin chooses to override.
Idempotent: verifying an already-verified member returns 200. The action is recorded in the org audit log (domain='org', action='member.verify') with the actor and target IDs so the override is traceable.
type PasswordHashUpdater ¶
type PasswordHashUpdater interface {
UpdatePasswordHash(ctx context.Context, userID string, newPassword []byte) error
}
PasswordHashUpdater updates the user's bcrypt hash in the database.
type PasswordResetHandler ¶
type PasswordResetHandler struct {
// contains filtered or unexported fields
}
PasswordResetHandler handles the password-reset-via-email flow.
func NewPasswordResetHandler ¶
func NewPasswordResetHandler( store passwordResetStore, users passwordResetUserLookup, keyInit passwordResetKeyInitializer, pwUpdate passwordResetPwUpdater, revoker passwordResetSessionRevoker, email *emailsvc.Service, log passwordResetLogger, ) *PasswordResetHandler
NewPasswordResetHandler constructs the handler. email may carry a nil provider (noop mode); in that case SendPasswordReset returns ErrNotConfigured and the request endpoint logs a warning but still returns 202 (the token is still created; the email is just not sent).
func (*PasswordResetHandler) Confirm ¶
func (h *PasswordResetHandler) Confirm(c *gin.Context)
Confirm handles POST /api/v1/auth/password-reset/confirm.
Public (the token IS the credential). Verifies the token hash, checks expiry + consumption, then executes the reset:
- Consume token (single-use)
- Update bcrypt hash (FIRST — avoids unrecoverable state if DEK reinit fails)
- Reinitialise DEK (old DEK unrecoverable without old password/recovery key)
- Revoke all outstanding sessions
- Send "password changed" notification email
Returns the new recovery key so the user can save it.
func (*PasswordResetHandler) Request ¶
func (h *PasswordResetHandler) Request(c *gin.Context)
Request handles POST /api/v1/auth/password-reset/request.
Always returns 202 (no email enumeration). Only sends a reset email if:
- the user exists
- the user's email is verified (don't send to unverified mailboxes)
func (*PasswordResetHandler) SetSecretPurger ¶
func (h *PasswordResetHandler) SetSecretPurger(p passwordResetSecretPurger)
SetSecretPurger wires the secret-row purger. Optional: when not set, reset does not delete the (already-cryptographically-erased) rows. Mirrors the setter-injection pattern of secrets.KeyService.SetSecretStore.
func (*PasswordResetHandler) SetWorkspaceNeutralizer ¶
func (h *PasswordResetHandler) SetWorkspaceNeutralizer(n passwordResetWorkspaceNeutralizer)
SetWorkspaceNeutralizer wires the workspace suspend/scrub step. Optional: when not set, reset does not touch running workspaces.
type PasswordVerifier ¶
type PasswordVerifier interface {
VerifyPassword(ctx context.Context, userID string, password []byte) error
}
PasswordVerifier confirms a user's password against the stored bcrypt hash. Used by RevealSecret to enforce a re-authentication gate before returning plaintext: a stolen JWT alone must not be sufficient to extract every secret. Implementations MUST run constant-time comparison (bcrypt.CompareHashAndPassword satisfies this) and MUST return a sentinel-typed error rather than the raw bcrypt error so the handler can map it to a uniform 403 without leaking timing or state information.
type PendingOrgCleaner ¶
type PendingOrgCleaner struct {
// contains filtered or unexported fields
}
PendingOrgCleaner reaps pending_activation orgs whose Stripe checkout was never completed. It runs on a ticker; each run lists stale pending orgs, verifies the checkout state with Stripe, and either activates (paid but webhook lost) or hard-deletes (checkout expired/canceled). On any Stripe API failure for a given org it skips that org and retries next cycle — never deletes an org it cannot verify.
func NewPendingOrgCleaner ¶
func NewPendingOrgCleaner(store pendingOrgStore, provider billing.CheckoutProvider, logger webHookLogger, interval, maxAge time.Duration) *PendingOrgCleaner
NewPendingOrgCleaner constructs the cleaner. interval is the tick period; maxAge is how old a pending_activation org must be before it is eligible. provider is used to build the default Stripe checkout lookup; pass nil only in tests that inject checkoutCompletedFn directly.
func (*PendingOrgCleaner) Run ¶
func (c *PendingOrgCleaner) Run(ctx context.Context)
Run blocks until ctx is canceled, reaping on each tick.
type PendingReloadLister ¶
type PendingReloadLister interface {
ListPendingReloadWorkspaces(ctx context.Context, userID string) ([]*types.WorkspaceMetadata, error)
}
PendingReloadLister lists workspaces with pending credential reload.
type PlatformAdminHandler ¶
type PlatformAdminHandler struct {
// contains filtered or unexported fields
}
PlatformAdminHandler implements the platform-admin org/user suspension endpoints (D19, US-43.19). All routes are mounted behind AuthMiddleware + AdminGuard (users.role='admin'), so every method here runs in a platform-admin context only.
func NewPlatformAdminHandler ¶
func NewPlatformAdminHandler(orgs platformAdminOrgStore, users platformAdminUserStore, authSvc orgAuthService, revoker platformUserRevoker, logger policyLogger) *PlatformAdminHandler
NewPlatformAdminHandler constructs the handler. revoker wires the F4 token revocation primitive (pass nil only in tests that do not exercise it). logger surfaces audit-write failures (audit is best-effort: a failed audit log never blocks the mutation, matching the existing policy/audit pattern).
func (*PlatformAdminHandler) ListOrgs ¶
func (h *PlatformAdminHandler) ListOrgs(c *gin.Context)
ListOrgs handles GET /api/v1/admin/orgs. Returns every organization with aggregated member + workspace counts for the platform-admin dashboard. Optional query params: limit (default 50, max 200), offset (>=0), status (one of pending_activation|active|suspended).
func (*PlatformAdminHandler) ListUsers ¶
func (h *PlatformAdminHandler) ListUsers(c *gin.Context)
ListUsers handles GET /api/v1/admin/users. Returns every user (sans password hash) with their single org membership resolved. Optional query params: limit (default 50, max 200), offset (>=0), status (active|suspended).
func (*PlatformAdminHandler) SuspendOrg ¶
func (h *PlatformAdminHandler) SuspendOrg(c *gin.Context)
SuspendOrg handles POST /api/v1/admin/orgs/:id/suspend.
Sets organizations.status='suspended'. Per D20 the operational effect (pod termination) is applied asynchronously by the controller querying the org-status cache on its next reconcile cycle — this endpoint only flips the authoritative status. The audit event is org-scoped so org admins see it in their own audit log.
func (*PlatformAdminHandler) SuspendUser ¶
func (h *PlatformAdminHandler) SuspendUser(c *gin.Context)
SuspendUser handles POST /api/v1/admin/users/:id/suspend.
Atomically refuses when the user is the sole active admin of any org (unless ?force=true), then sets users.status='suspended' and revokes the user's live tokens. The check + update run in one transaction (F7) so concurrent admin operations cannot orphan an org; the revocation marker (F4) makes the suspension take effect immediately, even during a DB blip.
func (*PlatformAdminHandler) UnsuspendOrg ¶
func (h *PlatformAdminHandler) UnsuspendOrg(c *gin.Context)
UnsuspendOrg handles POST /api/v1/admin/orgs/:id/unsuspend.
Sets organizations.status='active'. Per D20 the controller does NOT auto-resume workspaces; members/admins must manually resume each one.
func (*PlatformAdminHandler) UnsuspendUser ¶
func (h *PlatformAdminHandler) UnsuspendUser(c *gin.Context)
UnsuspendUser handles POST /api/v1/admin/users/:id/unsuspend.
type PodBootstrapHandler ¶
type PodBootstrapHandler struct {
// contains filtered or unexported fields
}
PodBootstrapHandler handles POST /internal/v1/pod-bootstrap — the secretless credential injection endpoint (Epic 35 US-35.3).
Auth is via K8s TokenReview (projected SA token, audience "llmsafespace-api"). No JWT middleware — the init container has no user identity. The handler verifies the SA name matches workspace-<workspaceID> AND the SA namespace matches the expected workspace namespace to enforce pod-to-workspace isolation: a compromised workspace pod can only retrieve its own credentials.
func NewPodBootstrapHandler ¶
func NewPodBootstrapHandler(reviewer TokenReviewer, injector bootstrapInjector, lookup bootstrapWorkspaceLookup, promptSvc *prompt.Service, expectedNamespace string) *PodBootstrapHandler
NewPodBootstrapHandler constructs the handler. In production, pass a *k8sTokenReviewer wrapping the API's K8s clientset. expectedNamespace is the K8s namespace where workspace ServiceAccounts live — validated against the SA namespace in the TokenReview username (S1 defense-in-depth).
func NewPodBootstrapHandlerFromClientset ¶
func NewPodBootstrapHandlerFromClientset(clientset kubernetes.Interface, injector bootstrapInjector, lookup bootstrapWorkspaceLookup, promptSvc *prompt.Service, expectedNamespace string) *PodBootstrapHandler
NewPodBootstrapHandlerFromClientset is the production constructor that wraps a kubernetes.Interface into a k8sTokenReviewer.
func (*PodBootstrapHandler) Bootstrap ¶
func (h *PodBootstrapHandler) Bootstrap(c *gin.Context)
Bootstrap handles POST /internal/v1/pod-bootstrap.
func (*PodBootstrapHandler) HasLogger ¶
func (h *PodBootstrapHandler) HasLogger() bool
HasLogger reports whether a logger has been wired. Used by the app-level wiring test to enforce that production constructs the handler with SetLogger called — without this, the underlying error in 5xx responses is silently dropped (the very gap PR #407 closed).
func (*PodBootstrapHandler) SetLogger ¶
func (h *PodBootstrapHandler) SetLogger(l interfaces.LoggerInterface)
SetLogger installs a structured logger so the handler can emit diagnostic events for 5xx responses. Without this, the handler returns a generic "secret preparation failed" body and the underlying error (e.g. "DEK not available", "decrypt failed", "DB timeout") is silently dropped — exactly the observability gap that turned the 2026-06-24 outage into a 30-minute diagnosis exercise instead of a 1-minute one.
The logger is optional: when nil, the handler falls back to a silent no-op so unit tests that don't care about log emission can omit it. Production wiring (api/internal/app/app.go) MUST install one — see TestPodBootstrapHandler_LoggerWired in api/internal/app/ for the regression guard that enforces this.
func (*PodBootstrapHandler) SetPromptService ¶
func (h *PodBootstrapHandler) SetPromptService(svc *prompt.Service)
SetPromptService wires the prompt resolution service after construction. Used when the prompt service is built later in the startup sequence.
type PodIPResolver ¶
type PodIPResolver interface {
GetWorkspacePodIP(ctx context.Context, userID, workspaceID string) (string, error)
}
PodIPResolver looks up the pod IP for a workspace.
type PolicyHandler ¶
type PolicyHandler struct {
// contains filtered or unexported fields
}
PolicyHandler handles GET/PUT/DELETE /api/v1/orgs/:id/policies (admin-only).
func NewPolicyHandler ¶
func NewPolicyHandler(store policyStore, svc *policy.Service, authSvc orgAuthService, logger policyLogger) *PolicyHandler
NewPolicyHandler constructs the handler. svc is used for cache invalidation after mutations. logger is used to surface audit emission failures.
func (*PolicyHandler) Delete ¶
func (h *PolicyHandler) Delete(c *gin.Context)
Delete handles DELETE /api/v1/orgs/:id/policies/:key. Removes a policy (reverts to default / unrestricted).
func (*PolicyHandler) Get ¶
func (h *PolicyHandler) Get(c *gin.Context)
Get handles GET /api/v1/orgs/:id/policies. Returns all configured policies.
func (*PolicyHandler) Put ¶
func (h *PolicyHandler) Put(c *gin.Context)
Put handles PUT /api/v1/orgs/:id/policies/:key. Upserts a single policy.
type ProbeModelEntry ¶
type ProbeModelEntry struct {
ID string `json:"id"`
ContextLimit int `json:"contextLimit"` // 0 = unknown / not configured
OutputLimit int `json:"outputLimit"` // 0 = unknown / not configured
}
ProbeModelEntry is one model returned by the probe endpoint.
type ProbeModelsRequest ¶
type ProbeModelsRequest struct {
APIKey string `json:"apiKey" binding:"required" log:"-"` //nolint:gosec // G117 false positive — field has log:"-" tag, never marshaled to response
BaseURL string `json:"baseURL" binding:"required"`
}
ProbeModelsRequest is the body for POST /api/v1/probe-models — a credential-free probe for use before a credential is saved.
type ProbeModelsResponse ¶
type ProbeModelsResponse struct {
Models []ProbeModelEntry `json:"models"`
BaseURL string `json:"baseURL,omitempty"`
// Warning is set when the /v1/models call failed. The response still
// succeeds (200) but Models is empty so the UI shows a friendly message.
Warning string `json:"warning,omitempty"`
}
ProbeModelsResponse is the response body for GET /:id/models.
type PromptHandler ¶
type PromptHandler struct {
// contains filtered or unexported fields
}
PromptHandler handles platform and org prompt CRUD endpoints.
func NewPromptHandler ¶
func NewPromptHandler(store promptStore, svc *prompt.Service, authSvc orgAuthService, logger policyLogger) *PromptHandler
NewPromptHandler constructs the handler.
func (*PromptHandler) GetOrg ¶
func (h *PromptHandler) GetOrg(c *gin.Context)
GetOrg handles GET /api/v1/orgs/:id/prompt.
func (*PromptHandler) GetPlatform ¶
func (h *PromptHandler) GetPlatform(c *gin.Context)
GetPlatform handles GET /api/v1/admin/prompt.
func (*PromptHandler) GetWorkspacePrompt ¶
func (h *PromptHandler) GetWorkspacePrompt(c *gin.Context)
GetWorkspacePrompt handles GET /api/v1/workspaces/:id/prompt.
func (*PromptHandler) SetOrg ¶
func (h *PromptHandler) SetOrg(c *gin.Context)
SetOrg handles PUT /api/v1/orgs/:id/prompt.
func (*PromptHandler) SetPlatform ¶
func (h *PromptHandler) SetPlatform(c *gin.Context)
SetPlatform handles PUT /api/v1/admin/prompt.
func (*PromptHandler) SetWorkspacePrompt ¶
func (h *PromptHandler) SetWorkspacePrompt(c *gin.Context)
SetWorkspacePrompt handles PUT /api/v1/workspaces/:id/prompt. Respects the org's allow_user_prompt toggle — returns 403 when locked.
type ProviderModelCost ¶
ProviderModelCost holds the per-token cost for a model. Zero on both fields identifies a free-tier model (e.g. opencode's built-in models).
type ProxyHandler ¶
type ProxyHandler struct {
// contains filtered or unexported fields
}
func NewProxyHandler ¶
func NewProxyHandler( k8sClient pkginterfaces.KubernetesClient, logger pkginterfaces.LoggerInterface, namespace string, httpClient *http.Client, dialect agent.Dialect, ) (*ProxyHandler, error)
func (*ProxyHandler) AbortSession ¶
func (h *ProxyHandler) AbortSession(c *gin.Context)
func (*ProxyHandler) BackfillSessionParents ¶
func (h *ProxyHandler) BackfillSessionParents(ctx context.Context, workspaceID string)
func (*ProxyHandler) CreateSession ¶
func (h *ProxyHandler) CreateSession(c *gin.Context)
func (*ProxyHandler) DeleteQueueMessage ¶
func (h *ProxyHandler) DeleteQueueMessage(c *gin.Context)
func (*ProxyHandler) DeleteSession ¶
func (h *ProxyHandler) DeleteSession(c *gin.Context)
func (*ProxyHandler) EnableSessionParentResolution ¶
func (h *ProxyHandler) EnableSessionParentResolution()
func (*ProxyHandler) EnqueueMessage ¶
func (h *ProxyHandler) EnqueueMessage(c *gin.Context)
func (*ProxyHandler) GetActiveSessions ¶
func (h *ProxyHandler) GetActiveSessions(ctx context.Context, workspaceID string) []string
GetActiveSessions returns the IDs of all sessions currently marked active for the workspace. Public because it is called from outside the handlers package (admin tooling, canary checks).
func (*ProxyHandler) GetAllKnownPhases ¶
func (h *ProxyHandler) GetAllKnownPhases() map[string]string
func (*ProxyHandler) GetBroker ¶
func (h *ProxyHandler) GetBroker() BrokerPublisher
func (*ProxyHandler) GetCachedPasswordForTest ¶
func (h *ProxyHandler) GetCachedPasswordForTest(workspaceID string) (string, bool)
GetCachedPasswordForTest returns whether a password is cached for the workspace (used by tests asserting cache invalidation).
func (*ProxyHandler) GetHistory ¶
func (h *ProxyHandler) GetHistory(c *gin.Context)
GetHistory returns a chronological page of displayable messages for a session.
Query parameters:
- limit: page size (default 50, max 200). Counts displayable messages only — system-role messages and messages whose parts collapse to nothing visible (e.g. only step-start/step-finish) do not count against the limit. Rejecting invalid limits (<=0 or non-numeric) surfaces client bugs early.
- before: opaque cursor — the message id of the OLDEST message in the previously-rendered page. Returns messages strictly older than this cursor. Absent => return the newest `limit` messages.
Response:
- body: JSON array of opencode message objects, oldest-first within the page. Schema preserved as-is so the frontend's transformHistory keeps working.
- X-Next-Cursor header: present iff more (older) messages exist; its value is the id of the OLDEST message in the returned page. Absent means there are no more messages to fetch.
The handler fetches the FULL upstream array from opencode (which does not paginate), filters to displayable messages server-side, then slices. Filtering server-side prevents jumpy page sizes that would otherwise happen if the frontend filtered after receiving the page.
func (*ProxyHandler) GetMessageQueueService ¶
func (h *ProxyHandler) GetMessageQueueService() interfaces.MessageQueueService
func (*ProxyHandler) GetPasswordGetter ¶
func (h *ProxyHandler) GetPasswordGetter() interfaces.WorkspacePasswordProvider
func (*ProxyHandler) GetPriorPhaseForTest ¶
func (h *ProxyHandler) GetPriorPhaseForTest(workspaceID string) (string, bool)
GetPriorPhaseForTest returns the prior-phase entry if present.
func (*ProxyHandler) GetSSETracker ¶
func (h *ProxyHandler) GetSSETracker() *sse.Tracker
func (*ProxyHandler) GetSession ¶
func (h *ProxyHandler) GetSession(c *gin.Context)
func (*ProxyHandler) GetWorkspaceConfigForTest ¶
func (h *ProxyHandler) GetWorkspaceConfigForTest(workspaceID string) (wsstate.Config, bool)
GetWorkspaceConfigForTest returns the cached config for the workspace.
func (*ProxyHandler) GetWorkspaceOwner ¶
func (h *ProxyHandler) GetWorkspaceOwner(workspaceID string) string
func (*ProxyHandler) HasActiveWorkspaceForTest ¶
func (h *ProxyHandler) HasActiveWorkspaceForTest(workspaceID string) bool
HasActiveWorkspaceForTest reports whether the workspace currently has any active sessions (i.e. an active set was created and is non-empty). Used by tests asserting that the per-workspace entry is cleaned up after the last session is removed.
func (*ProxyHandler) ListPermissions ¶
func (h *ProxyHandler) ListPermissions(c *gin.Context)
ListPermissions proxies GET /permission to the workspace pod.
func (*ProxyHandler) ListQuestions ¶
func (h *ProxyHandler) ListQuestions(c *gin.Context)
ListQuestions proxies GET /question to the workspace pod.
func (*ProxyHandler) ListQueue ¶
func (h *ProxyHandler) ListQueue(c *gin.Context)
func (*ProxyHandler) ListSessions ¶
func (h *ProxyHandler) ListSessions(c *gin.Context)
func (*ProxyHandler) MarkSessionDeletedForTest ¶
func (h *ProxyHandler) MarkSessionDeletedForTest(workspaceID, sessionID string)
MarkSessionDeletedForTest seeds a deleted-session tombstone.
func (*ProxyHandler) PermissionReply ¶
func (h *ProxyHandler) PermissionReply(c *gin.Context)
PermissionReply proxies POST /permission/:requestID/reply to the workspace pod.
func (*ProxyHandler) QuestionReject ¶
func (h *ProxyHandler) QuestionReject(c *gin.Context)
QuestionReject proxies POST /question/:requestID/reject to the workspace pod.
func (*ProxyHandler) QuestionReply ¶
func (h *ProxyHandler) QuestionReply(c *gin.Context)
QuestionReply proxies POST /question/:requestID/reply to the workspace pod.
func (*ProxyHandler) RenameSessionInAgent ¶
func (h *ProxyHandler) RenameSessionInAgent(ctx context.Context, workspaceID, sessionID, title string) error
RenameSessionInAgent sends a title update to the opencode agent running on the workspace pod so that the agent's in-memory session title matches the user-assigned title. Without this, the periodic title fetch (useSessionTitle hook in the frontend) retrieves the old agent-side title and overwrites the user's rename in PostgreSQL.
func (*ProxyHandler) SendMessage ¶
func (h *ProxyHandler) SendMessage(c *gin.Context)
func (*ProxyHandler) SendPromptAsync ¶
func (h *ProxyHandler) SendPromptAsync(c *gin.Context)
func (*ProxyHandler) SetActiveSessionsForTest ¶
func (h *ProxyHandler) SetActiveSessionsForTest(workspaceID string, sessionIDs []string)
SetActiveSessionsForTest seeds the active-session set for the workspace. Test-only — production callers must use CheckAndAddActiveSession so the maxSessions limit is enforced atomically. Kept as a public method on ProxyHandler so existing tests that poked the activeSess map can be migrated with a one-line change.
Contract: the maxSessions argument passed to CheckAndAddActiveSession is `len(sessionIDs)+1` so all seeds succeed regardless of duplicate IDs in the input. Tests that need to exercise oversubscribe handling (i.e. seed a state that violates the maxSessions invariant) must call CheckAndAddActiveSession directly, not this helper.
func (*ProxyHandler) SetAgentStateChecker ¶
func (h *ProxyHandler) SetAgentStateChecker(c AgentStateChecker)
func (*ProxyHandler) SetCachedPasswordForTest ¶
func (h *ProxyHandler) SetCachedPasswordForTest(workspaceID, password string)
SetCachedPasswordForTest seeds the password cache for a workspace.
func (*ProxyHandler) SetMessageQueueService ¶
func (h *ProxyHandler) SetMessageQueueService(svc interfaces.MessageQueueService)
func (*ProxyHandler) SetMeteringService ¶
func (h *ProxyHandler) SetMeteringService(svc interfaces.MeteringService)
func (*ProxyHandler) SetParentBackfilledForTest ¶
func (h *ProxyHandler) SetParentBackfilledForTest(workspaceID string)
SetParentBackfilledForTest seeds the parent-backfill marker.
func (*ProxyHandler) SetPriorPhaseForTest ¶
func (h *ProxyHandler) SetPriorPhaseForTest(workspaceID, phase string)
SetPriorPhaseForTest seeds the prior-phase entry.
func (*ProxyHandler) SetRequestBufferConfig ¶
func (h *ProxyHandler) SetRequestBufferConfig(maxSize int, timeout time.Duration)
SetRequestBufferConfig rebuilds the per-workspace request buffer with the configured size and timeout. Must be called before Start: request goroutines read h.requestBuffer without synchronization, so a late swap would race. Values <=0 fall back to the enabled defaults (size 10, timeout 30s) so the feature is on unless explicitly constructed disabled — the zero-value config must not silently turn buffering off in production.
func (*ProxyHandler) SetSessionIndex ¶
func (h *ProxyHandler) SetSessionIndex(si interfaces.SessionIndexService)
func (*ProxyHandler) SetStateStore ¶
func (h *ProxyHandler) SetStateStore(store wsstate.Store)
SetStateStore overrides the per-workspace state store. By default the ProxyHandler uses an InMemoryStore (single-replica); app.go swaps in a RedisStore when a Redis/Valkey client is available so multi-replica deployments share active-session state. Panics if called after Start() — request goroutines read stateStore without synchronization, so a late swap would race.
func (*ProxyHandler) SetSweepInterval ¶ added in v0.2.0
func (h *ProxyHandler) SetSweepInterval(d time.Duration)
SetSweepInterval overrides the periodic queue-sweep interval (default 30s). Primarily for tests that need the goroutine to fire quickly. Must be called before Start().
func (*ProxyHandler) SetVersionSyncCallback ¶
func (h *ProxyHandler) SetVersionSyncCallback(cb workspace.VersionSyncCallback)
func (*ProxyHandler) SetWorkspaceConfigForTest ¶
func (h *ProxyHandler) SetWorkspaceConfigForTest(workspaceID string, cfg wsstate.Config)
SetWorkspaceConfigForTest seeds the workspace-config cache.
func (*ProxyHandler) SetWorkspaceUpdateCallback ¶
func (h *ProxyHandler) SetWorkspaceUpdateCallback(cb workspace.WorkspaceUpdateCallback)
SetWorkspaceUpdateCallback installs the per-CRD-event callback that powers the watcher-driven auto-push of user-DEK secrets after pod recreation (worklog 0591). Must be called before Start().
func (*ProxyHandler) Start ¶
func (h *ProxyHandler) Start() error
func (*ProxyHandler) Stop ¶
func (h *ProxyHandler) Stop() error
func (*ProxyHandler) StreamEvents ¶
func (h *ProxyHandler) StreamEvents(c *gin.Context)
func (*ProxyHandler) StreamUserEvents ¶
func (h *ProxyHandler) StreamUserEvents(c *gin.Context)
StreamUserEvents is the user-scoped SSE endpoint (GET /api/v1/events). It delivers workspace.phase events for ALL of the user's workspaces.
func (*ProxyHandler) WorkspacePassword ¶
WorkspacePassword implements interfaces.WorkspacePasswordProvider (US-46.11).
type QueueClearer ¶
type QueueClearer interface {
PeekAllWorkspace(ctx context.Context, workspaceID string) ([]msgqueue.QueuedMessage, error)
ClearWorkspace(ctx context.Context, workspaceID string) error
}
QueueClearer is the minimal queue interface needed by the reload handler.
type RelayAdminHandler ¶
type RelayAdminHandler struct {
// contains filtered or unexported fields
}
RelayAdminHandler serves the relay admin setup wizard and status dashboard endpoints. The relay fleet supports AWS (paid primary), OCI (free secondary), and GCP (optional) providers — matching the InferenceRelay CRD enum `aws;oci;gcp`.
func NewRelayAdminHandler ¶
func NewRelayAdminHandler(clientset kubernetes.Interface, llmClient interfaces.LLMSafespacesV1Interface, namespace, routerNamespace, routerSvcURL string) *RelayAdminHandler
NewRelayAdminHandler creates a new relay admin handler. namespace is the workspace namespace (for Secrets, CRDs). routerNamespace is the namespace where the relay-router Deployment lives.
func (*RelayAdminHandler) Deploy ¶
func (h *RelayAdminHandler) Deploy(c *gin.Context)
Deploy creates or updates the InferenceRelay CR. Valid providers are "aws", "oci", and "gcp" — matching the CRD enum validation.
func (*RelayAdminHandler) GetSetup ¶
func (h *RelayAdminHandler) GetSetup(c *gin.Context)
GetSetup returns the prerequisite checklist state for the relay setup wizard.
The checklist is network-stack agnostic: it verifies LLMSafeSpaces-owned prerequisites (relay-router Deployment, InferenceRelay CRD, provider credentials) but does NOT probe the network path between the router and relay VMs. Post-WG-removal (worklog 0442) the router dials relay VMs by public IP over HTTP with per-VM token auth; reachability is verified downstream via instance health in GetStatus.
func (*RelayAdminHandler) GetStatus ¶
func (h *RelayAdminHandler) GetStatus(c *gin.Context)
GetStatus returns the full fleet status by aggregating CR status + router metrics.
func (*RelayAdminHandler) Pause ¶
func (h *RelayAdminHandler) Pause(c *gin.Context)
Pause pauses the relay fleet — stops provisioning/replacing VMs.
func (*RelayAdminHandler) Resume ¶
func (h *RelayAdminHandler) Resume(c *gin.Context)
Resume removes the pause annotation — controller resumes provisioning/replacing VMs.
func (*RelayAdminHandler) Rotate ¶
func (h *RelayAdminHandler) Rotate(c *gin.Context)
Rotate triggers manual rotation of a specific relay instance.
func (*RelayAdminHandler) SaveAWSCreds ¶
func (h *RelayAdminHandler) SaveAWSCreds(c *gin.Context)
SaveAWSCreds saves AWS IAM access key credentials for EC2 provisioning.
func (*RelayAdminHandler) SaveGCPCreds ¶
func (h *RelayAdminHandler) SaveGCPCreds(c *gin.Context)
SaveGCPCreds saves GCP service account JSON to a K8s Secret.
func (*RelayAdminHandler) SaveOCICreds ¶
func (h *RelayAdminHandler) SaveOCICreds(c *gin.Context)
SaveOCICreds saves OCI credentials to a K8s Secret.
func (*RelayAdminHandler) SetHTTPClient ¶
func (h *RelayAdminHandler) SetHTTPClient(client *http.Client)
SetHTTPClient overrides the HTTP client (for testing).
type RelayStateChecker ¶
RelayStateChecker returns whether the relay injector has completed for the given workspace. Production implementation resolves podIP + password and calls /v1/readyz on the agentd admin port (4098). This is separate from AgentClient (which targets opencode port 4096 with Basic auth) because the relay check uses the admin port with Bearer auth.
type RotateKeyHandler ¶
type RotateKeyHandler struct {
// contains filtered or unexported fields
}
RotateKeyHandler handles account key management endpoints.
func NewRotateKeyHandler ¶
func NewRotateKeyHandler(keySvc KeyRotator) *RotateKeyHandler
NewRotateKeyHandler creates a new RotateKeyHandler.
func (*RotateKeyHandler) ChangePassword ¶
func (h *RotateKeyHandler) ChangePassword(c *gin.Context)
ChangePassword handles POST /api/v1/account/change-password
func (*RotateKeyHandler) RecoverAccount ¶
func (h *RotateKeyHandler) RecoverAccount(c *gin.Context)
RecoverAccount handles POST /api/v1/account/recover
func (*RotateKeyHandler) RotateKey ¶
func (h *RotateKeyHandler) RotateKey(c *gin.Context)
RotateKey handles POST /api/v1/account/rotate-key. On success the response includes the new keyVersion AND a freshly- issued recoveryKey: the old recovery key wraps the now-discarded old DEK, so the user must save the new one. This is a one-time display — the API does not store it anywhere recoverable.
func (*RotateKeyHandler) SetAuditFunc ¶
func (h *RotateKeyHandler) SetAuditFunc(f func(userID, action string))
SetAuditFunc sets an optional audit callback for key operations.
func (*RotateKeyHandler) SetPasswordUpdater ¶
func (h *RotateKeyHandler) SetPasswordUpdater(u PasswordHashUpdater)
SetPasswordUpdater sets the optional password hash updater.
type SSOHandler ¶
type SSOHandler struct {
// contains filtered or unexported fields
}
SSOHandler exposes both the org-admin SSO config CRUD and the public OIDC login flow (start/callback) and domain discovery. The OIDC mechanics live in the sso.Service; this handler owns HTTP concerns (cookies, redirects, response shaping).
func NewSSOHandler ¶
func NewSSOHandler(svc *sso.Service, store ssoStore, authSvc orgAuthService, sessionCookie, cookieDomain, frontendURL string, logger ssoLogger) *SSOHandler
NewSSOHandler constructs the handler. sessionCookie is the JWT cookie name used elsewhere in the app (e.g. "lsp_session"); frontendURL is the post-SSO browser landing URL (may be empty — the handler falls back to "/"). cookieDomain is the Domain attribute for the session cookie (empty = host-only; set when wildcard subdomain routing is enabled so the session survives the IdP→callback→subdomain redirect chain).
func (*SSOHandler) Callback ¶
func (h *SSOHandler) Callback(c *gin.Context)
Callback handles GET /api/v1/auth/sso/:orgSlug/callback (public). Exchanges the code, sets the session JWT cookie, and redirects to the frontend.
func (*SSOHandler) Delete ¶
func (h *SSOHandler) Delete(c *gin.Context)
Delete handles DELETE /api/v1/orgs/:id/sso (org admin).
func (*SSOHandler) Domains ¶
func (h *SSOHandler) Domains(c *gin.Context)
Domains handles GET /api/v1/auth/sso/domains (public). Returns every claimed SSO domain for login-page discovery.
func (*SSOHandler) Get ¶
func (h *SSOHandler) Get(c *gin.Context)
Get handles GET /api/v1/orgs/:id/sso (org admin).
func (*SSOHandler) OIDCEnabled ¶
func (h *SSOHandler) OIDCEnabled(ctx context.Context) bool
OIDCEnabled reports whether any org has configured SSO, for the /auth/config feature flag. A DB error is treated as "disabled" so a transient failure does not advertise SSO when there is none to complete.
func (*SSOHandler) Put ¶
func (h *SSOHandler) Put(c *gin.Context)
Put handles PUT /api/v1/orgs/:id/sso (org admin). Upserts the SSO config.
func (*SSOHandler) RotateToken ¶
func (h *SSOHandler) RotateToken(c *gin.Context)
RotateToken handles POST /api/v1/orgs/:id/sso/verification-token/rotate (org admin). Replaces the DNS verification token with a fresh random value. Used for both initial creation (when no token exists) and rotation.
func (*SSOHandler) Start ¶
func (h *SSOHandler) Start(c *gin.Context)
Start handles GET /api/v1/auth/sso/:orgSlug/start (public). Redirects the browser to the IdP authorization endpoint and sets the signed PKCE/state cookie.
func (*SSOHandler) VerifyDomain ¶
func (h *SSOHandler) VerifyDomain(c *gin.Context)
VerifyDomain handles POST /api/v1/orgs/:id/sso/domains/:domain/verify (org admin). On-demand DNS verification: checks the TXT record at _llmsafespaces-verify.<domain> for the org's verification token and promotes the domain to verified on match.
type SecretsHandler ¶
type SecretsHandler struct {
// contains filtered or unexported fields
}
SecretsHandler handles HTTP requests for the secrets API.
func NewSecretsHandler ¶
func NewSecretsHandler(svc *secrets.SecretService) *SecretsHandler
NewSecretsHandler creates a new SecretsHandler.
func (*SecretsHandler) CreateSecret ¶
func (h *SecretsHandler) CreateSecret(c *gin.Context)
CreateSecret handles POST /api/v1/secrets
func (*SecretsHandler) DeleteSecret ¶
func (h *SecretsHandler) DeleteSecret(c *gin.Context)
DeleteSecret handles DELETE /api/v1/secrets/:id
func (*SecretsHandler) GetAuditLog ¶
func (h *SecretsHandler) GetAuditLog(c *gin.Context)
GetAuditLog handles GET /api/v1/secrets/audit
func (*SecretsHandler) GetBindings ¶
func (h *SecretsHandler) GetBindings(c *gin.Context)
GetBindings handles GET /api/v1/workspaces/:id/bindings
func (*SecretsHandler) GetSecret ¶
func (h *SecretsHandler) GetSecret(c *gin.Context)
GetSecret handles GET /api/v1/secrets/:id
func (*SecretsHandler) GetSecretBindings ¶
func (h *SecretsHandler) GetSecretBindings(c *gin.Context)
GetSecretBindings handles GET /api/v1/secrets/:id/bindings
func (*SecretsHandler) HasPodIPResolver ¶
func (h *SecretsHandler) HasPodIPResolver() bool
HasPodIPResolver reports whether a PodIPResolver has been configured. Used by wiring tests to verify the handler is fully constructed; without a resolver the reload-secrets endpoint and the SetBindings auto-push silently no-op (Bug 1 + Bug 2 in worklog 0085).
func (*SecretsHandler) ListSecrets ¶
func (h *SecretsHandler) ListSecrets(c *gin.Context)
ListSecrets handles GET /api/v1/secrets
func (*SecretsHandler) ReloadSecrets ¶
func (h *SecretsHandler) ReloadSecrets(c *gin.Context)
ReloadSecrets handles POST /api/v1/workspaces/:id/reload-secrets Decrypts bound secrets and pushes them to the running pod's agentd.
Two failure classes get different HTTP status codes:
- InjectSecrets failures (bad workspaceID, DEK unavailable, wrapped ciphertext corrupted) are mapped by handleSecretError to 400/403/500.
- Push transport / agentd failures map to 503/409/502.
Both flow through the same shared agentpush.Service (constructed once by the wiring layer, reused across every request) so on-wire behavior matches SetBindings and the workspace-service auto-push exactly. agentpush wraps InjectSecrets errors with "inject secrets: %w", so we can unwrap to recover the typed error for handleSecretError.
func (*SecretsHandler) RevealSecret ¶
func (h *SecretsHandler) RevealSecret(c *gin.Context)
RevealSecret handles POST /api/v1/secrets/:id/reveal Requires password reconfirmation: a stolen JWT alone must not be sufficient to extract every secret. Without a configured PasswordVerifier the handler returns 503 — shipping without verification is exactly the security theater the validator audit flagged. The bcrypt.CompareHashAndPassword call inside the verifier is constant-time, so failed-password timing does not differentiate from missing-DEK timing in practice.
func (*SecretsHandler) SetAgentPusher ¶
func (h *SecretsHandler) SetAgentPusher(p *agentpush.Service)
SetAgentPusher installs a pre-built agentpush.Service. Preferred over SetPodIPResolver + SetModelCache + SetLogger for new call sites, and used by app.New to share a single pusher instance across the handler and workspace.Service (the pod-recreation auto-push consumer).
func (*SecretsHandler) SetBindings ¶
func (h *SecretsHandler) SetBindings(c *gin.Context)
func (*SecretsHandler) SetCredentialStateWriter ¶
func (h *SecretsHandler) SetCredentialStateWriter(w CredentialStateWriter)
SetCredentialStateWriter installs the writer. If nil, MarkCredentialChanged is silently skipped (banner won't appear but no crash).
func (*SecretsHandler) SetLogger ¶
func (h *SecretsHandler) SetLogger(l pkginterfaces.LoggerInterface)
SetLogger installs the logger used to surface non-fatal failures from the bind-time auto-push. Optional; if nil, failures are silent (which is exactly Bug 2 in worklog 0085 — do not leave nil in production).
func (*SecretsHandler) SetModelCache ¶
func (h *SecretsHandler) SetModelCache(c ModelCache)
SetModelCache injects the shared model cache so SecretsHandler can evict a workspace's cache entry after credential binds (M2-a: replaces the former package-level global defaultModelCache).
func (*SecretsHandler) SetPasswordVerifier ¶
func (h *SecretsHandler) SetPasswordVerifier(v PasswordVerifier)
SetPasswordVerifier installs the verifier used to confirm the caller's password on RevealSecret. If left nil the reveal handler rejects every request with 503; this is intentional because shipping without password verification is exactly the security theater we fixed (validator finding on RevealSecret in worklog 0094 audit).
func (*SecretsHandler) SetPodIPResolver ¶
func (h *SecretsHandler) SetPodIPResolver(r PodIPResolver)
SetPodIPResolver sets the resolver for looking up pod IPs.
func (*SecretsHandler) UpdateSecret ¶
func (h *SecretsHandler) UpdateSecret(c *gin.Context)
UpdateSecret handles PUT /api/v1/secrets/:id
type SettingsHandler ¶
type SettingsHandler struct {
// contains filtered or unexported fields
}
SettingsHandler handles admin and user settings API requests.
func NewSettingsHandler ¶
func NewSettingsHandler(instanceSvc *settings.InstanceService, userSvc *settings.UserService) *SettingsHandler
NewSettingsHandler creates a new settings handler.
func (*SettingsHandler) GetAdminSettings ¶
func (h *SettingsHandler) GetAdminSettings(c *gin.Context)
GetAdminSettings returns all instance settings merged with defaults.
func (*SettingsHandler) GetAdminSettingsSchema ¶
func (h *SettingsHandler) GetAdminSettingsSchema(c *gin.Context)
GetAdminSettingsSchema returns the full Tier 2 schema definition.
func (*SettingsHandler) GetUserSettings ¶
func (h *SettingsHandler) GetUserSettings(c *gin.Context)
GetUserSettings returns all user settings merged with defaults.
func (*SettingsHandler) GetUserSettingsSchema ¶
func (h *SettingsHandler) GetUserSettingsSchema(c *gin.Context)
GetUserSettingsSchema returns the full Tier 3 schema definition.
func (*SettingsHandler) SetAdminSetting ¶
func (h *SettingsHandler) SetAdminSetting(c *gin.Context)
SetAdminSetting updates a single instance setting.
func (*SettingsHandler) SetUserSetting ¶
func (h *SettingsHandler) SetUserSetting(c *gin.Context)
SetUserSetting updates a single user setting.
type StripeWebhookHandler ¶
type StripeWebhookHandler struct {
// contains filtered or unexported fields
}
StripeWebhookHandler receives and processes Stripe webhook deliveries. It verifies the HMAC signature, deduplicates via stripe_events, and dispatches by event type. It has no JWT auth — the signature is the credential.
func NewStripeWebhookHandler ¶
func NewStripeWebhookHandler(provider billing.CheckoutProvider, store stripeEventStore, logger webHookLogger) *StripeWebhookHandler
NewStripeWebhookHandler constructs the handler. provider must be able to verify webhook signatures (i.e. a *billing.StripeProvider, not the noop).
func (*StripeWebhookHandler) HandleWebhook ¶
func (h *StripeWebhookHandler) HandleWebhook(c *gin.Context)
HandleWebhook is POST /api/v1/webhooks/stripe.
type TerminalCache ¶
type TerminalCache interface {
Get(ctx context.Context, key string) (string, error)
Set(ctx context.Context, key, value string, exp time.Duration) error
Delete(ctx context.Context, key string) error
}
TerminalCache is the subset of CacheService needed by the terminal handler.
type TerminalHandler ¶
type TerminalHandler struct {
// contains filtered or unexported fields
}
TerminalHandler handles WebSocket terminal connections to workspace pods.
func NewTerminalHandler ¶
func NewTerminalHandler( cache TerminalCache, wsGetter WorkspaceGetter, namespace string, logger pkginterfaces.LoggerInterface, ) *TerminalHandler
NewTerminalHandler creates a new terminal handler.
func (*TerminalHandler) HandleTerminal ¶
func (h *TerminalHandler) HandleTerminal(c *gin.Context)
HandleTerminal handles GET /workspaces/:id/terminal?ticket=<ticket>.
func (*TerminalHandler) HandleTicket ¶
func (h *TerminalHandler) HandleTicket(c *gin.Context)
HandleTicket handles POST /workspaces/:id/terminal/ticket.
func (*TerminalHandler) SetExecConfig ¶
func (h *TerminalHandler) SetExecConfig(cfg *rest.Config, cs kubernetes.Interface)
SetExecConfig sets the K8s config for pod exec (call after construction).
type TerminalMessage ¶
type TerminalMessage struct {
Type string `json:"type"` // input, resize, output, exit, error
Data string `json:"data,omitempty"`
Cols uint16 `json:"cols,omitempty"`
Rows uint16 `json:"rows,omitempty"`
Code int `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
TerminalMessage is the JSON frame for WebSocket communication.
type TicketResponse ¶
TicketResponse is returned by POST /terminal/ticket.
type TokenReviewer ¶
TokenReviewer validates a projected ServiceAccount token via K8s TokenReview. Returns the authenticated username (e.g. "system:serviceaccount:<ns>:workspace-<id>") on success.
type UnlockDEKHandler ¶
type UnlockDEKHandler struct {
// contains filtered or unexported fields
}
UnlockDEKHandler handles POST /api/v1/auth/unlock-dek (Epic 56).
The "soft" in soft-unlock means: no JWT invalidation, no full re-login. The user re-enters their password to repopulate the per-session DEK when the durable rehydrate path fails for one of the residual cases the design enumerates (pre-feature backfill, US-50.4 DEK rotation, row corruption). On success the durable jwt_sessions row is rewritten under the user's MATCHED signing key — not the active key — so that a subsequent rehydrate after Valkey restart still works under the same JWT.
func NewUnlockDEKHandler ¶
func NewUnlockDEKHandler(keys DEKUnlocker) *UnlockDEKHandler
NewUnlockDEKHandler creates a handler with the given key-service.
func (*UnlockDEKHandler) Unlock ¶
func (h *UnlockDEKHandler) Unlock(c *gin.Context)
Unlock handles POST /auth/unlock-dek.
type UsageHandler ¶
type UsageHandler struct {
// contains filtered or unexported fields
}
func NewUsageHandler ¶
func NewUsageHandler(meteringSvc interfaces.MeteringService, dbSvc interfaces.DatabaseService) *UsageHandler
func (*UsageHandler) AdminBillingStatus ¶
func (h *UsageHandler) AdminBillingStatus(c *gin.Context)
func (*UsageHandler) AdminDiscardDLQ ¶
func (h *UsageHandler) AdminDiscardDLQ(c *gin.Context)
func (*UsageHandler) AdminGetDLQ ¶
func (h *UsageHandler) AdminGetDLQ(c *gin.Context)
func (*UsageHandler) AdminGetUsage ¶
func (h *UsageHandler) AdminGetUsage(c *gin.Context)
func (*UsageHandler) AdminRetryDLQ ¶
func (h *UsageHandler) AdminRetryDLQ(c *gin.Context)
func (*UsageHandler) GetQuotaStatus ¶
func (h *UsageHandler) GetQuotaStatus(c *gin.Context)
func (*UsageHandler) GetUsage ¶
func (h *UsageHandler) GetUsage(c *gin.Context)
func (*UsageHandler) GetWorkspaceUsage ¶
func (h *UsageHandler) GetWorkspaceUsage(c *gin.Context)
func (*UsageHandler) SetDB ¶
func (h *UsageHandler) SetDB(db *sql.DB)
type UserProviderCredentialsHandler ¶
type UserProviderCredentialsHandler struct {
// contains filtered or unexported fields
}
UserProviderCredentialsHandler handles user-scoped provider credential CRUD.
func NewUserProviderCredentialsHandler ¶
func NewUserProviderCredentialsHandler(store CredentialStore, bindings CredentialBindingStore, keys *secrets.KeyService, keyStore secrets.KeyStore) *UserProviderCredentialsHandler
NewUserProviderCredentialsHandler creates a new handler. The CRUD store provides owner-scoped credential rows; the binding store handles the user-only credential↔workspace operations.
func (*UserProviderCredentialsHandler) Bind ¶
func (h *UserProviderCredentialsHandler) Bind(c *gin.Context)
Bind handles POST /api/v1/provider-credentials/:id/bind/:workspaceId.
func (*UserProviderCredentialsHandler) Create ¶
func (h *UserProviderCredentialsHandler) Create(c *gin.Context)
Create handles POST /api/v1/provider-credentials.
NOTE — DEK rotation (L-2 known limitation): User credentials are encrypted with the user's DEK at creation time. If the user later rotates their password (re-wrapping the DEK), existing provider credentials in provider_credentials are NOT re-encrypted, because the server cannot access the old DEK without an active session holding it. Credentials whose key_version is stale will fail to decrypt after a DEK rotation. A future improvement should re-encrypt provider_credentials as part of the password-rotation flow.
func (*UserProviderCredentialsHandler) Delete ¶
func (h *UserProviderCredentialsHandler) Delete(c *gin.Context)
Delete handles DELETE /api/v1/provider-credentials/:id. Notifies all workspaces that had this credential bound so running pods pick up the revocation on their next secret reload (C-3 fix).
func (*UserProviderCredentialsHandler) Get ¶
func (h *UserProviderCredentialsHandler) Get(c *gin.Context)
Get handles GET /api/v1/provider-credentials/:id.
func (*UserProviderCredentialsHandler) List ¶
func (h *UserProviderCredentialsHandler) List(c *gin.Context)
List handles GET /api/v1/provider-credentials.
func (*UserProviderCredentialsHandler) ListBindings ¶
func (h *UserProviderCredentialsHandler) ListBindings(c *gin.Context)
ListBindings handles GET /api/v1/provider-credentials/:id/bindings. Returns workspace IDs with their binding source type (explicit vs auto) so the UI can show which workspaces have user-initiated vs seeded bindings (M-1 fix).
func (*UserProviderCredentialsHandler) ProbeModels ¶
func (h *UserProviderCredentialsHandler) ProbeModels(c *gin.Context)
ProbeModels handles GET /api/v1/provider-credentials/:id/models. User variant — uses the session DEK to decrypt.
func (*UserProviderCredentialsHandler) SetCredentialStateWriter ¶
func (h *UserProviderCredentialsHandler) SetCredentialStateWriter(w CredentialStateWriter)
SetCredentialStateWriter installs the reload banner trigger.
func (*UserProviderCredentialsHandler) SetWorkspaceOwnerChecker ¶
func (h *UserProviderCredentialsHandler) SetWorkspaceOwnerChecker(fn WorkspaceOwnerChecker)
SetWorkspaceOwnerChecker installs the ownership verification function.
func (*UserProviderCredentialsHandler) Unbind ¶
func (h *UserProviderCredentialsHandler) Unbind(c *gin.Context)
Unbind handles DELETE /api/v1/provider-credentials/:id/bind/:workspaceId. Returns 409 Conflict if the binding is auto-managed (H-1 fix: auto-bindings protected).
type WorkspaceEnvHandler ¶
type WorkspaceEnvHandler struct {
// contains filtered or unexported fields
}
WorkspaceEnvHandler handles PUT/GET/DELETE /api/v1/workspaces/:id/env. Extracted from SecretsHandler (US-29.4) — env-var management is a distinct responsibility from secret CRUD, binding management, and model selection. Each env var is stored as an env-secret-bound-to-workspace; the handler orchestrates create-or-update-by-name + binding management.
func NewWorkspaceEnvHandler ¶
func NewWorkspaceEnvHandler(svc WorkspaceEnvService) *WorkspaceEnvHandler
NewWorkspaceEnvHandler creates a WorkspaceEnvHandler backed by the given service. The service must be non-nil — it is a required dependency (US-29.8 principle: fail at construction, not at request time).
func (*WorkspaceEnvHandler) DeleteWorkspaceEnv ¶
func (h *WorkspaceEnvHandler) DeleteWorkspaceEnv(c *gin.Context)
DeleteWorkspaceEnv handles DELETE /api/v1/workspaces/:id/env/:name
func (*WorkspaceEnvHandler) GetWorkspaceEnv ¶
func (h *WorkspaceEnvHandler) GetWorkspaceEnv(c *gin.Context)
GetWorkspaceEnv handles GET /api/v1/workspaces/:id/env Returns env var names (never values) bound to this workspace.
func (*WorkspaceEnvHandler) SetLogger ¶
func (h *WorkspaceEnvHandler) SetLogger(l pkginterfaces.LoggerInterface)
SetLogger installs the logger used to surface non-fatal failures from secret create/update/delete operations. Optional; if nil, failures are silent (do not leave nil in production).
func (*WorkspaceEnvHandler) SetWorkspaceEnv ¶
func (h *WorkspaceEnvHandler) SetWorkspaceEnv(c *gin.Context)
SetWorkspaceEnv handles PUT /api/v1/workspaces/:id/env
Creates or updates env-secret type secrets bound to this workspace.
Concurrency: SetWorkspaceEnv only ADDs bindings (it never removes — that's what DeleteWorkspaceEnv is for), so we can use the store's AddBindings primitive which holds a workspace-scoped advisory lock for the duration of the binding write. Two concurrent SetWorkspaceEnv calls on the same workspace serialize at the AddBindings step and neither's secrets are lost.
Error handling: every UpdateSecret/CreateSecret/AddBindings failure surfaces as 500 with the offending var name. Pre-fix the handler returned 204 even when the writes silently failed.
type WorkspaceEnvService ¶
type WorkspaceEnvService interface {
// GetSecretByName returns the named secret for the user.
//
// Contract: a not-found secret is reported as (nil, nil) — NOT
// (nil, ErrNotFound). SetWorkspaceEnv and DeleteWorkspaceEnv rely on
// this: they branch on `existing != nil` to decide create-vs-update
// (and to short-circuit a no-op delete), so any implementation that
// returned a non-nil error for the absence case would surface as a
// spurious 500 and break env-var creation. Backed by
// pg_secret_store.go, which maps pgx.ErrNoRows → (nil, nil).
GetSecretByName(ctx context.Context, userID, name string) (*secrets.SecretResponse, error)
UpdateSecret(ctx context.Context, userID, sessionID string, matchedSigningKey []byte, secretID string, req secrets.UpdateSecretRequest) error
CreateSecret(ctx context.Context, userID, sessionID string, matchedSigningKey []byte, req secrets.CreateSecretRequest) (*secrets.SecretResponse, error)
AddBindings(ctx context.Context, userID, workspaceID string, secretIDs []string) (secrets.BindingsMutationResult, error)
GetBindings(ctx context.Context, userID, workspaceID string) (*secrets.BindingsResponse, error)
DeleteSecret(ctx context.Context, userID, secretID string) error
}
WorkspaceEnvService is the caller-shaped subset of SecretService methods used by the workspace-env endpoints. *secrets.SecretService satisfies it.
type WorkspaceGetter ¶
type WorkspaceGetter interface {
GetWorkspace(ctx context.Context, id string) (*v1.Workspace, error)
}
WorkspaceGetter resolves workspace CRDs.
type WorkspaceOwnerChecker ¶
WorkspaceOwnerChecker verifies workspace ownership for bind operations.
Source Files
¶
- admin_provider_credentials.go
- admin_session.go
- agent_drain.go
- agent_reload.go
- agent_roles.go
- audit.go
- auth_unlock.go
- binding_errors.go
- credential_ops.go
- credential_probe.go
- email.go
- email_verify.go
- errors.go
- internal_org_status.go
- invitations.go
- login_discovery.go
- model_catalog.go
- models.go
- models_handler.go
- org_billing.go
- org_credentials.go
- org_sso.go
- orgs.go
- password_reset.go
- pending_org_cleaner.go
- platform_admin.go
- pod_bootstrap.go
- policies.go
- prompts.go
- proxy.go
- proxy_chat_enrichment.go
- proxy_connections.go
- proxy_events.go
- proxy_handlers.go
- proxy_helpers.go
- proxy_input.go
- proxy_lifecycle.go
- proxy_permissions.go
- proxy_request_buffer.go
- proxy_session_index.go
- proxy_stream.go
- proxy_upstream_observability.go
- relay_admin.go
- secrets.go
- session_parents.go
- settings.go
- stream_user_events.go
- terminal.go
- usage.go
- user_provider_credentials.go
- webhook.go
- workspace_env.go