Changeset 0.38.1 (#234)
Co-authored-by: gobha <jasafpro@gmail.com> Co-committed-by: gobha <jasafpro@gmail.com>
This commit is contained in:
101
server/handlers/connection_resolver.go
Normal file
101
server/handlers/connection_resolver.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package handlers
|
||||
|
||||
// v0.38.1: ConnectionResolver bridges the sandbox connections module
|
||||
// to the store + vault layers. Implements sandbox.ConnectionResolver.
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"chat-switchboard/crypto"
|
||||
"chat-switchboard/models"
|
||||
"chat-switchboard/store"
|
||||
)
|
||||
|
||||
// ConnectionResolverAdapter implements sandbox.ConnectionResolver.
|
||||
type ConnectionResolverAdapter struct {
|
||||
stores store.Stores
|
||||
vault *crypto.KeyResolver
|
||||
}
|
||||
|
||||
func NewConnectionResolverAdapter(s store.Stores, vault *crypto.KeyResolver) *ConnectionResolverAdapter {
|
||||
return &ConnectionResolverAdapter{stores: s, vault: vault}
|
||||
}
|
||||
|
||||
// Resolve returns a single connection with secrets decrypted.
|
||||
func (r *ConnectionResolverAdapter) Resolve(ctx context.Context, userID, connType, name string) (*models.ExtConnection, error) {
|
||||
conn, err := r.stores.Connections.Resolve(ctx, userID, connType, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.decryptInPlace(ctx, conn)
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// ResolveAll returns all accessible connections with secrets decrypted.
|
||||
func (r *ConnectionResolverAdapter) ResolveAll(ctx context.Context, userID, connType string) ([]models.ExtConnection, error) {
|
||||
conns, err := r.stores.Connections.ResolveAll(ctx, userID, connType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range conns {
|
||||
r.decryptInPlace(ctx, &conns[i])
|
||||
}
|
||||
return conns, nil
|
||||
}
|
||||
|
||||
// decryptInPlace decrypts secret fields in the connection's config.
|
||||
func (r *ConnectionResolverAdapter) decryptInPlace(ctx context.Context, conn *models.ExtConnection) {
|
||||
if conn == nil || conn.Config == nil || r.vault == nil {
|
||||
return
|
||||
}
|
||||
secretFields := r.lookupSecretFields(ctx, conn.PackageID, conn.Type)
|
||||
for k, v := range conn.Config {
|
||||
if !secretFields[k] {
|
||||
continue
|
||||
}
|
||||
m, ok := v.(map[string]interface{})
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
enc := toBytes(m["enc"])
|
||||
nonce := toBytes(m["nonce"])
|
||||
if len(enc) == 0 {
|
||||
continue
|
||||
}
|
||||
plaintext, err := r.vault.Decrypt(enc, nonce, conn.Scope, conn.OwnerID)
|
||||
if err == nil {
|
||||
conn.Config[k] = plaintext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// lookupSecretFields reads the package manifest to identify secret fields.
|
||||
func (r *ConnectionResolverAdapter) lookupSecretFields(ctx context.Context, packageID, connType string) map[string]bool {
|
||||
result := map[string]bool{}
|
||||
pkg, err := r.stores.Packages.Get(ctx, packageID)
|
||||
if err != nil || pkg == nil {
|
||||
return result
|
||||
}
|
||||
connsRaw, ok := pkg.Manifest["connections"]
|
||||
if !ok {
|
||||
return result
|
||||
}
|
||||
connsJSON, _ := json.Marshal(connsRaw)
|
||||
var conns []struct {
|
||||
Type string `json:"type"`
|
||||
Fields map[string]map[string]string `json:"fields"`
|
||||
}
|
||||
json.Unmarshal(connsJSON, &conns)
|
||||
for _, cd := range conns {
|
||||
if cd.Type == connType {
|
||||
for fieldName, fieldDef := range cd.Fields {
|
||||
if fieldDef["type"] == "secret" {
|
||||
result[fieldName] = true
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user