mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-13 00:14:33 +00:00
Squash merge PR #115 after resolving changelog and SQLite migration-map conflicts with current dev. Renumbered channel-context PostgreSQL migration to 000075 and bumped PG required schema to 75 plus SQLite schema to 44 so it follows the run timeline migration. Local checks passed: go test ./..., go build ./..., go build -tags sqliteonly ./..., go vet ./..., and pnpm -C ui/web build. PR CI run 26705617311 passed release-versioning, go, and web.
529 lines
19 KiB
Go
529 lines
19 KiB
Go
//go:build sqlite || sqliteonly
|
|
|
|
package sqlitestore
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/crypto"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
func resolveContextChannelInstanceID(ctx context.Context, db *sql.DB, scope store.ChannelContextScope) (uuid.UUID, error) {
|
|
if scope.ChannelInstanceID != uuid.Nil {
|
|
return scope.ChannelInstanceID, nil
|
|
}
|
|
if scope.ChannelInstanceName == "" {
|
|
return uuid.Nil, sql.ErrNoRows
|
|
}
|
|
var id uuid.UUID
|
|
err := db.QueryRowContext(ctx,
|
|
`SELECT id FROM channel_instances WHERE tenant_id = ? AND name = ?`,
|
|
tenantIDForInsert(ctx), scope.ChannelInstanceName,
|
|
).Scan(&id)
|
|
return id, err
|
|
}
|
|
|
|
func validateChannelScope(scopeType, scopeKey string) error {
|
|
switch scopeType {
|
|
case store.ChannelScopeTypeChannel:
|
|
return nil
|
|
case store.ChannelScopeTypeGroup, store.ChannelScopeTypeUser, store.ChannelScopeTypeRole:
|
|
if scopeKey == "" {
|
|
return fmt.Errorf("scope_key required for %s scope", scopeType)
|
|
}
|
|
return nil
|
|
default:
|
|
return fmt.Errorf("invalid scope_type: %s", scopeType)
|
|
}
|
|
}
|
|
|
|
func rawPtrOrNull(raw *json.RawMessage) any {
|
|
if raw == nil || len(*raw) == 0 {
|
|
return nil
|
|
}
|
|
return string(*raw)
|
|
}
|
|
|
|
func jsonOrEmptyObject(raw json.RawMessage) any {
|
|
if len(raw) == 0 {
|
|
return "{}"
|
|
}
|
|
return string(raw)
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) UpsertContextGrant(ctx context.Context, g *store.MCPContextGrant) error {
|
|
if err := validateChannelScope(g.ScopeType, g.ScopeKey); err != nil {
|
|
return err
|
|
}
|
|
if err := store.ValidateUserID(g.GrantedBy); err != nil {
|
|
return err
|
|
}
|
|
if g.ID == uuid.Nil {
|
|
g.ID = store.GenNewID()
|
|
}
|
|
now := time.Now().UTC().Format(time.RFC3339Nano)
|
|
_, err := s.db.ExecContext(ctx,
|
|
`INSERT INTO mcp_context_grants
|
|
(id, tenant_id, channel_instance_id, scope_type, scope_key, server_id, enabled,
|
|
tool_allow, tool_deny, config_overrides, granted_by, created_at, updated_at)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
ON CONFLICT (tenant_id, channel_instance_id, scope_type, scope_key, server_id)
|
|
DO UPDATE SET enabled = excluded.enabled,
|
|
tool_allow = excluded.tool_allow,
|
|
tool_deny = excluded.tool_deny,
|
|
config_overrides = excluded.config_overrides,
|
|
granted_by = excluded.granted_by,
|
|
updated_at = excluded.updated_at`,
|
|
g.ID, tenantIDForInsert(ctx), g.ChannelInstanceID, g.ScopeType, g.ScopeKey, g.ServerID, g.Enabled,
|
|
jsonOrNull(g.ToolAllow), jsonOrNull(g.ToolDeny), jsonOrNull(g.ConfigOverrides),
|
|
g.GrantedBy, now, now,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) DeleteContextGrant(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, serverID uuid.UUID) error {
|
|
_, err := s.db.ExecContext(ctx,
|
|
`DELETE FROM mcp_context_grants
|
|
WHERE tenant_id = ? AND channel_instance_id = ? AND scope_type = ? AND scope_key = ? AND server_id = ?`,
|
|
tenantIDForInsert(ctx), channelInstanceID, scopeType, scopeKey, serverID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) ListContextGrants(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string) ([]store.MCPContextGrant, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT id, channel_instance_id, scope_type, scope_key, server_id, enabled,
|
|
COALESCE(tool_allow, 'null'), COALESCE(tool_deny, 'null'),
|
|
COALESCE(config_overrides, 'null'), granted_by, created_at, updated_at
|
|
FROM mcp_context_grants
|
|
WHERE tenant_id = ? AND channel_instance_id = ? AND scope_type = ? AND scope_key = ?
|
|
ORDER BY created_at`,
|
|
tenantIDForInsert(ctx), channelInstanceID, scopeType, scopeKey,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
result := make([]store.MCPContextGrant, 0)
|
|
for rows.Next() {
|
|
var g store.MCPContextGrant
|
|
var createdAt, updatedAt sqliteTime
|
|
if err := rows.Scan(&g.ID, &g.ChannelInstanceID, &g.ScopeType, &g.ScopeKey, &g.ServerID, &g.Enabled,
|
|
&g.ToolAllow, &g.ToolDeny, &g.ConfigOverrides, &g.GrantedBy, &createdAt, &updatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
g.CreatedAt = createdAt.Time
|
|
g.UpdatedAt = updatedAt.Time
|
|
result = append(result, g)
|
|
}
|
|
return result, rows.Err()
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) ListContextGrantsForScope(ctx context.Context, scope store.ChannelContextScope) ([]store.MCPContextGrant, error) {
|
|
id, err := resolveContextChannelInstanceID(ctx, s.db, scope)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.ListContextGrants(ctx, id, scope.ScopeType, scope.ScopeKey)
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) SetContextCredentials(ctx context.Context, creds *store.MCPContextCredentials) error {
|
|
if err := validateChannelScope(creds.ScopeType, creds.ScopeKey); err != nil {
|
|
return err
|
|
}
|
|
if err := store.ValidateUserID(creds.CreatedBy); err != nil {
|
|
return err
|
|
}
|
|
if creds.ID == uuid.Nil {
|
|
creds.ID = store.GenNewID()
|
|
}
|
|
var apiKey sql.NullString
|
|
if creds.APIKey != "" && s.encKey != "" {
|
|
enc, err := crypto.Encrypt(creds.APIKey, s.encKey)
|
|
if err != nil {
|
|
return fmt.Errorf("encrypt mcp context api_key: %w", err)
|
|
}
|
|
apiKey = sql.NullString{String: enc, Valid: true}
|
|
} else if creds.APIKey != "" {
|
|
apiKey = sql.NullString{String: creds.APIKey, Valid: true}
|
|
}
|
|
var headersEnc, envEnc []byte
|
|
if len(creds.Headers) > 0 {
|
|
raw, _ := json.Marshal(creds.Headers)
|
|
headersEnc = s.encryptJSON(raw)
|
|
}
|
|
if len(creds.Env) > 0 {
|
|
raw, _ := json.Marshal(creds.Env)
|
|
envEnc = s.encryptJSON(raw)
|
|
}
|
|
now := time.Now().UTC().Format(time.RFC3339Nano)
|
|
_, err := s.db.ExecContext(ctx,
|
|
`INSERT INTO mcp_context_credentials
|
|
(id, tenant_id, channel_instance_id, scope_type, scope_key, server_id,
|
|
api_key, headers, env, created_by, created_at, updated_at)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
|
ON CONFLICT (tenant_id, channel_instance_id, scope_type, scope_key, server_id)
|
|
DO UPDATE SET api_key = excluded.api_key,
|
|
headers = excluded.headers,
|
|
env = excluded.env,
|
|
created_by = excluded.created_by,
|
|
updated_at = excluded.updated_at`,
|
|
creds.ID, tenantIDForInsert(ctx), creds.ChannelInstanceID, creds.ScopeType, creds.ScopeKey, creds.ServerID,
|
|
apiKey, headersEnc, envEnc, creds.CreatedBy, now, now,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) GetContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, serverID uuid.UUID) (*store.MCPContextCredentials, error) {
|
|
row := s.db.QueryRowContext(ctx,
|
|
`SELECT id, channel_instance_id, scope_type, scope_key, server_id, api_key, headers, env, created_by, created_at, updated_at
|
|
FROM mcp_context_credentials
|
|
WHERE tenant_id = ? AND channel_instance_id = ? AND scope_type = ? AND scope_key = ? AND server_id = ?`,
|
|
tenantIDForInsert(ctx), channelInstanceID, scopeType, scopeKey, serverID,
|
|
)
|
|
creds, err := s.scanContextCredentials(row)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
return creds, err
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) GetContextCredentialsForScope(ctx context.Context, scope store.ChannelContextScope, serverID uuid.UUID) (*store.MCPContextCredentials, error) {
|
|
id, err := resolveContextChannelInstanceID(ctx, s.db, scope)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.GetContextCredentials(ctx, id, scope.ScopeType, scope.ScopeKey, serverID)
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) DeleteContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, serverID uuid.UUID) error {
|
|
_, err := s.db.ExecContext(ctx,
|
|
`DELETE FROM mcp_context_credentials
|
|
WHERE tenant_id = ? AND channel_instance_id = ? AND scope_type = ? AND scope_key = ? AND server_id = ?`,
|
|
tenantIDForInsert(ctx), channelInstanceID, scopeType, scopeKey, serverID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) ListContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string) ([]store.MCPContextCredentials, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT id, channel_instance_id, scope_type, scope_key, server_id, api_key, headers, env, created_by, created_at, updated_at
|
|
FROM mcp_context_credentials
|
|
WHERE tenant_id = ? AND channel_instance_id = ? AND scope_type = ? AND scope_key = ?
|
|
ORDER BY created_at`,
|
|
tenantIDForInsert(ctx), channelInstanceID, scopeType, scopeKey,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
result := make([]store.MCPContextCredentials, 0)
|
|
for rows.Next() {
|
|
creds, err := s.scanContextCredentials(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, *creds)
|
|
}
|
|
return result, rows.Err()
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) ListContextCredentialsForScope(ctx context.Context, scope store.ChannelContextScope) ([]store.MCPContextCredentials, error) {
|
|
id, err := resolveContextChannelInstanceID(ctx, s.db, scope)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.ListContextCredentials(ctx, id, scope.ScopeType, scope.ScopeKey)
|
|
}
|
|
|
|
type contextCredentialScanner interface {
|
|
Scan(dest ...any) error
|
|
}
|
|
|
|
func (s *SQLiteMCPServerStore) scanContextCredentials(row contextCredentialScanner) (*store.MCPContextCredentials, error) {
|
|
var creds store.MCPContextCredentials
|
|
var apiKey sql.NullString
|
|
var headersEnc, envEnc []byte
|
|
var createdAt, updatedAt sqliteTime
|
|
if err := row.Scan(&creds.ID, &creds.ChannelInstanceID, &creds.ScopeType, &creds.ScopeKey, &creds.ServerID,
|
|
&apiKey, &headersEnc, &envEnc, &creds.CreatedBy, &createdAt, &updatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
creds.CreatedAt = createdAt.Time
|
|
creds.UpdatedAt = updatedAt.Time
|
|
if apiKey.Valid && apiKey.String != "" && s.encKey != "" {
|
|
if dec, err := crypto.Decrypt(apiKey.String, s.encKey); err == nil {
|
|
creds.APIKey = dec
|
|
}
|
|
} else if apiKey.Valid {
|
|
creds.APIKey = apiKey.String
|
|
}
|
|
if len(headersEnc) > 0 {
|
|
_ = json.Unmarshal(s.decryptJSON(headersEnc), &creds.Headers)
|
|
}
|
|
if len(envEnc) > 0 {
|
|
_ = json.Unmarshal(s.decryptJSON(envEnc), &creds.Env)
|
|
}
|
|
return &creds, nil
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) UpsertContextGrant(ctx context.Context, g *store.SecureCLIContextGrant) error {
|
|
if err := validateChannelScope(g.ScopeType, g.ScopeKey); err != nil {
|
|
return err
|
|
}
|
|
if err := store.ValidateUserID(g.GrantedBy); err != nil {
|
|
return err
|
|
}
|
|
if g.ID == uuid.Nil {
|
|
g.ID = store.GenNewID()
|
|
}
|
|
var envBytes []byte
|
|
if len(g.EncryptedEnv) > 0 && s.encKey != "" {
|
|
encrypted, err := crypto.Encrypt(string(g.EncryptedEnv), s.encKey)
|
|
if err != nil {
|
|
return fmt.Errorf("encrypt context grant env: %w", err)
|
|
}
|
|
envBytes = []byte(encrypted)
|
|
} else {
|
|
envBytes = g.EncryptedEnv
|
|
}
|
|
now := time.Now().UTC().Format(time.RFC3339Nano)
|
|
_, err := s.db.ExecContext(ctx,
|
|
`INSERT INTO secure_cli_context_grants
|
|
(id, tenant_id, channel_instance_id, scope_type, scope_key, binary_id,
|
|
deny_args, deny_verbose, timeout_seconds, tips, encrypted_env, enabled,
|
|
granted_by, created_at, updated_at)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
ON CONFLICT (tenant_id, channel_instance_id, scope_type, scope_key, binary_id)
|
|
DO UPDATE SET deny_args = excluded.deny_args,
|
|
deny_verbose = excluded.deny_verbose,
|
|
timeout_seconds = excluded.timeout_seconds,
|
|
tips = excluded.tips,
|
|
encrypted_env = excluded.encrypted_env,
|
|
enabled = excluded.enabled,
|
|
granted_by = excluded.granted_by,
|
|
updated_at = excluded.updated_at`,
|
|
g.ID, tenantIDForInsert(ctx), g.ChannelInstanceID, g.ScopeType, g.ScopeKey, g.BinaryID,
|
|
rawPtrOrNull(g.DenyArgs), rawPtrOrNull(g.DenyVerbose), g.TimeoutSeconds, g.Tips, envBytes, g.Enabled,
|
|
g.GrantedBy, now, now,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) DeleteContextGrant(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, binaryID uuid.UUID) error {
|
|
_, err := s.db.ExecContext(ctx,
|
|
`DELETE FROM secure_cli_context_grants
|
|
WHERE tenant_id = ? AND channel_instance_id = ? AND scope_type = ? AND scope_key = ? AND binary_id = ?`,
|
|
tenantIDForInsert(ctx), channelInstanceID, scopeType, scopeKey, binaryID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) ListContextGrants(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string) ([]store.SecureCLIContextGrant, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT id, channel_instance_id, scope_type, scope_key, binary_id,
|
|
deny_args, deny_verbose, timeout_seconds, tips, encrypted_env,
|
|
enabled, granted_by, created_at, updated_at
|
|
FROM secure_cli_context_grants
|
|
WHERE tenant_id = ? AND channel_instance_id = ? AND scope_type = ? AND scope_key = ?
|
|
ORDER BY created_at`,
|
|
tenantIDForInsert(ctx), channelInstanceID, scopeType, scopeKey,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
result := make([]store.SecureCLIContextGrant, 0)
|
|
for rows.Next() {
|
|
g, err := s.scanSecureCLIContextGrant(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, *g)
|
|
}
|
|
return result, rows.Err()
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) ListContextGrantsForScope(ctx context.Context, scope store.ChannelContextScope) ([]store.SecureCLIContextGrant, error) {
|
|
id, err := resolveContextChannelInstanceID(ctx, s.db, scope)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.ListContextGrants(ctx, id, scope.ScopeType, scope.ScopeKey)
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) scanSecureCLIContextGrant(row contextCredentialScanner) (*store.SecureCLIContextGrant, error) {
|
|
var g store.SecureCLIContextGrant
|
|
var denyArgs, denyVerbose []byte
|
|
var createdAt, updatedAt sqliteTime
|
|
if err := row.Scan(&g.ID, &g.ChannelInstanceID, &g.ScopeType, &g.ScopeKey, &g.BinaryID,
|
|
&denyArgs, &denyVerbose, &g.TimeoutSeconds, &g.Tips, &g.EncryptedEnv, &g.Enabled,
|
|
&g.GrantedBy, &createdAt, &updatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
g.CreatedAt = createdAt.Time
|
|
g.UpdatedAt = updatedAt.Time
|
|
if len(denyArgs) > 0 {
|
|
raw := json.RawMessage(denyArgs)
|
|
g.DenyArgs = &raw
|
|
}
|
|
if len(denyVerbose) > 0 {
|
|
raw := json.RawMessage(denyVerbose)
|
|
g.DenyVerbose = &raw
|
|
}
|
|
if len(g.EncryptedEnv) > 0 && s.encKey != "" {
|
|
if decrypted, err := crypto.Decrypt(string(g.EncryptedEnv), s.encKey); err == nil {
|
|
g.EncryptedEnv = []byte(decrypted)
|
|
}
|
|
}
|
|
g.EnvSet = len(g.EncryptedEnv) > 0
|
|
return &g, nil
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) SetContextCredentials(ctx context.Context, creds *store.SecureCLIContextCredentials) error {
|
|
if err := validateChannelScope(creds.ScopeType, creds.ScopeKey); err != nil {
|
|
return err
|
|
}
|
|
if err := store.ValidateUserID(creds.CreatedBy); err != nil {
|
|
return err
|
|
}
|
|
if creds.ID == uuid.Nil {
|
|
creds.ID = store.GenNewID()
|
|
}
|
|
var envBytes []byte
|
|
if len(creds.EncryptedEnv) > 0 && s.encKey != "" {
|
|
encrypted, err := crypto.Encrypt(string(creds.EncryptedEnv), s.encKey)
|
|
if err != nil {
|
|
return fmt.Errorf("encrypt context credential env: %w", err)
|
|
}
|
|
envBytes = []byte(encrypted)
|
|
} else {
|
|
envBytes = creds.EncryptedEnv
|
|
}
|
|
now := time.Now().UTC().Format(time.RFC3339Nano)
|
|
_, err := s.db.ExecContext(ctx,
|
|
`INSERT INTO secure_cli_context_credentials
|
|
(id, tenant_id, channel_instance_id, scope_type, scope_key, binary_id,
|
|
encrypted_env, metadata, credential_type, host_scope, created_by, created_at, updated_at)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
ON CONFLICT (tenant_id, channel_instance_id, scope_type, scope_key, binary_id)
|
|
DO UPDATE SET encrypted_env = excluded.encrypted_env,
|
|
metadata = excluded.metadata,
|
|
credential_type = excluded.credential_type,
|
|
host_scope = excluded.host_scope,
|
|
created_by = excluded.created_by,
|
|
updated_at = excluded.updated_at`,
|
|
creds.ID, tenantIDForInsert(ctx), creds.ChannelInstanceID, creds.ScopeType, creds.ScopeKey, creds.BinaryID,
|
|
envBytes, jsonOrEmptyObject(creds.Metadata), creds.CredentialType, creds.HostScope, creds.CreatedBy, now, now,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) GetContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, binaryID uuid.UUID) (*store.SecureCLIContextCredentials, error) {
|
|
row := s.db.QueryRowContext(ctx,
|
|
`SELECT id, channel_instance_id, scope_type, scope_key, binary_id, encrypted_env,
|
|
COALESCE(metadata, '{}'), credential_type, host_scope, created_by, created_at, updated_at
|
|
FROM secure_cli_context_credentials
|
|
WHERE tenant_id = ? AND channel_instance_id = ? AND scope_type = ? AND scope_key = ? AND binary_id = ?`,
|
|
tenantIDForInsert(ctx), channelInstanceID, scopeType, scopeKey, binaryID,
|
|
)
|
|
creds, err := s.scanSecureCLIContextCredentials(row)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
return creds, err
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) GetContextCredentialsForScope(ctx context.Context, scope store.ChannelContextScope, binaryID uuid.UUID) (*store.SecureCLIContextCredentials, error) {
|
|
id, err := resolveContextChannelInstanceID(ctx, s.db, scope)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.GetContextCredentials(ctx, id, scope.ScopeType, scope.ScopeKey, binaryID)
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) DeleteContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string, binaryID uuid.UUID) error {
|
|
_, err := s.db.ExecContext(ctx,
|
|
`DELETE FROM secure_cli_context_credentials
|
|
WHERE tenant_id = ? AND channel_instance_id = ? AND scope_type = ? AND scope_key = ? AND binary_id = ?`,
|
|
tenantIDForInsert(ctx), channelInstanceID, scopeType, scopeKey, binaryID,
|
|
)
|
|
return err
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) ListContextCredentials(ctx context.Context, channelInstanceID uuid.UUID, scopeType, scopeKey string) ([]store.SecureCLIContextCredentials, error) {
|
|
rows, err := s.db.QueryContext(ctx,
|
|
`SELECT id, channel_instance_id, scope_type, scope_key, binary_id, encrypted_env,
|
|
COALESCE(metadata, '{}'), credential_type, host_scope, created_by, created_at, updated_at
|
|
FROM secure_cli_context_credentials
|
|
WHERE tenant_id = ? AND channel_instance_id = ? AND scope_type = ? AND scope_key = ?
|
|
ORDER BY created_at`,
|
|
tenantIDForInsert(ctx), channelInstanceID, scopeType, scopeKey,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer rows.Close()
|
|
result := make([]store.SecureCLIContextCredentials, 0)
|
|
for rows.Next() {
|
|
creds, err := s.scanSecureCLIContextCredentials(rows)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
result = append(result, *creds)
|
|
}
|
|
return result, rows.Err()
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) ListContextCredentialsForScope(ctx context.Context, scope store.ChannelContextScope) ([]store.SecureCLIContextCredentials, error) {
|
|
id, err := resolveContextChannelInstanceID(ctx, s.db, scope)
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return nil, nil
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.ListContextCredentials(ctx, id, scope.ScopeType, scope.ScopeKey)
|
|
}
|
|
|
|
func (s *SQLiteSecureCLIStore) scanSecureCLIContextCredentials(row contextCredentialScanner) (*store.SecureCLIContextCredentials, error) {
|
|
var creds store.SecureCLIContextCredentials
|
|
var createdAt, updatedAt sqliteTime
|
|
if err := row.Scan(&creds.ID, &creds.ChannelInstanceID, &creds.ScopeType, &creds.ScopeKey, &creds.BinaryID,
|
|
&creds.EncryptedEnv, &creds.Metadata, &creds.CredentialType, &creds.HostScope,
|
|
&creds.CreatedBy, &createdAt, &updatedAt); err != nil {
|
|
return nil, err
|
|
}
|
|
creds.CreatedAt = createdAt.Time
|
|
creds.UpdatedAt = updatedAt.Time
|
|
if len(creds.EncryptedEnv) > 0 && s.encKey != "" {
|
|
if decrypted, err := crypto.Decrypt(string(creds.EncryptedEnv), s.encKey); err == nil {
|
|
creds.EncryptedEnv = []byte(decrypted)
|
|
}
|
|
}
|
|
return &creds, nil
|
|
}
|