Skip to content

updated sdk manager routes - #1

Merged
asifalix merged 1 commit into
mainfrom
sdk-mgr-consolidation
Mar 13, 2026
Merged

updated sdk manager routes#1
asifalix merged 1 commit into
mainfrom
sdk-mgr-consolidation

Conversation

@asifalix

Copy link
Copy Markdown
Contributor

No description provided.

Copilot AI review requested due to automatic review settings March 13, 2026 00:44
@asifalix
asifalix merged commit 1a7ebe5 into main Mar 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR migrates the former standalone “sdk-manager” functionality into the main AuthSec API by adding new SDK manager services/controllers and registering their HTTP routes under /authsec/sdkmgr/* (plus a backward-compat /sdkmgr/* alias).

Changes:

  • Adds SDK manager service layer: MCP OAuth/PKCE auth + session storage, playground conversation/server management, SPIRE proxying, and a voice auth client.
  • Adds SDK manager controllers and route registration for all new endpoints under /authsec/sdkmgr/* and /sdkmgr/*.
  • Introduces new DB tables/migrations for OAuth sessions and playground artifacts, plus new config knobs for OAuth + Azure OpenAI.

Reviewed changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 17 comments.

Show a summary per file
File Description
services/sdkmgr/voice_client_service.go Voice assistant service integrating Azure OpenAI chat/TTS with CIBA/TOTP flows
services/sdkmgr/spire_proxy_service.go SPIRE agent proxy service (tenant mapping + socket probe/placeholder SVID)
services/sdkmgr/services_service.go Session-based lookup service for external service credentials and user details
services/sdkmgr/retry.go Generic retry helper with exponential backoff + jitter
services/sdkmgr/pkce.go PKCE verifier/challenge/state generation utilities
services/sdkmgr/oauth_session_store.go OAuth session persistence across master + tenant DBs (migration + lookup helpers)
services/sdkmgr/mcp_tools_manager.go Generates MCP tool schemas and enforces session_id requirements in schemas
services/sdkmgr/mcp_playground_service.go Playground conversation CRUD, chat completion, MCP server CRUD (stubs)
services/sdkmgr/mcp_oauth_service.go OAuth discovery + authorization + callback HTML handling for MCP servers
services/sdkmgr/mcp_auth_service.go Core MCP OAuth flow + RBAC tool protection and tool list handling
services/sdkmgr/jwt_decode.go JWT payload decode helper (no signature verification)
services/sdkmgr/dev_server_service.go Runs user-provided MCP server code as a Python subprocess (stdio JSON-RPC)
services/sdkmgr/dashboard_service.go Dashboard analytics queries for oauth_sessions/users
services/sdkmgr/client_id.go Normalization/candidate generation for client_id variants
services/sdkmgr/circuit_breaker.go Named circuit breaker registry using gobreaker
routes/routes.go Registers sdkmgr routes under /authsec/sdkmgr/* and alias /sdkmgr/*
models/sdkmgr/playground.go GORM models for playground conversations/messages/servers
models/sdkmgr/oauth_session.go GORM model for oauth_sessions + helpers for token/scopes/tools
migrations/tenant/012_create_playground_tables.sql Tenant DB migration: playground tables
migrations/tenant/011_create_oauth_sessions.sql Tenant DB migration: oauth_sessions table
migrations/master/003_create_oauth_sessions.sql Master DB migration: oauth_sessions table
go.mod Adds gobreaker dependency (currently marked indirect)
go.sum Records gobreaker checksums
controllers/sdkmgr/voice_controller.go HTTP handlers for voice interact/poll/tts
controllers/sdkmgr/spire_controller.go HTTP handlers for SPIRE workload endpoints
controllers/sdkmgr/services_controller.go HTTP handlers for service credentials/user-details
controllers/sdkmgr/mcp_playground_controller.go HTTP handlers for playground CRUD/chat/server mgmt
controllers/sdkmgr/mcp_oauth_controller.go HTTP handlers for MCP OAuth check/authorize/callback/refresh
controllers/sdkmgr/mcp_auth_controller.go HTTP handlers for MCP auth flow/session/tools/protection
controllers/sdkmgr/dev_server_controller.go HTTP handlers for dev MCP server start/stop/status
controllers/sdkmgr/dashboard_controller.go HTTP handlers for dashboard statistics/admin-users
config/config.go Adds sdkmgr + Azure OpenAI config fields and env loading

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +69 to +80
body, _ := json.Marshal(payload)
resp, err := http.Post(endpoint, "application/json", bytes.NewReader(body)) //nolint:gosec
if err != nil {
logrus.Errorf("CIBA Initiate error: %v", err)
return map[string]interface{}{"error": err.Error(), "auth_req_id": nil}
}
defer resp.Body.Close()

var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return map[string]interface{}{"error": "decode error", "auth_req_id": nil}
}
Comment on lines +109 to +122
body, _ := json.Marshal(payload)
resp, err := http.Post(endpoint, "application/json", bytes.NewReader(body)) //nolint:gosec
if err != nil {
a.mu.Lock()
a.retryCounts[email]++
remaining := 3 - a.retryCounts[email]
a.mu.Unlock()
return map[string]interface{}{"success": false, "error": err.Error(), "remaining": remaining}
}
defer resp.Body.Close()

var resData map[string]interface{}
json.NewDecoder(resp.Body).Decode(&resData)

Comment on lines +398 to +399
aiMsg := chatResp["choices"].([]interface{})[0].(map[string]interface{})["message"].(map[string]interface{})
history = append(history, aiMsg)
Comment on lines +375 to +386
// Select SDK based on client_id
sdk := s.authSDK
if clientID != "" && clientID != s.authSDK.clientID {
sdk = newAuthSecSDK(clientID)
}

history := s.getHistory(email)
history = append(history, map[string]interface{}{"role": "user", "content": userInput})
history = trimHistory(history)
// Update system prompt with latest retry count
history[0]["content"] = s.systemPrompt(email)

Comment thread config/config.go
Comment on lines +250 to +254
azureOpenAIKey := getEnv("AZURE_OPENAI_API_KEY", "")
azureOpenAIEndpoint := getEnv("AZURE_OPENAI_ENDPOINT", "")
azureOpenAIDeployment := getEnv("AZURE_OPENAI_DEPLOYMENT", "")
azureOpenAIVersion := getEnv("AZURE_OPENAI_VERSION", "2024-02-15-preview")
azureOpenAITTSDeployment := getEnv("AZURE_OPENAI_TTS_DEPLOYMENT", "tts")
Comment on lines +74 to +101
cfg := config.AppConfig
if cfg != nil && cfg.PKCEChallenge != "" {
challenge = cfg.PKCEChallenge
}

session.PKCEVerifier = &verifier
session.PKCEChallenge = &challenge
session.OAuthState = &state

if err := s.SessionStore.SaveSession(session); err != nil {
return nil, fmt.Errorf("failed to save session: %w", err)
}

// Build authorization URL.
redirectURI := s.resolveRedirectURI(resolvedClientID)

params := url.Values{
"response_type": {"code"},
"client_id": {resolvedClientID},
"redirect_uri": {redirectURI},
"scope": {"openid profile email"},
"state": {state},
"code_challenge": {challenge},
"code_challenge_method": {"S256"},
}

authURL := cfg.OAuthAuthURL + "?" + params.Encode()

Comment on lines +61 to +132
// getServiceID looks up the service ID from the tenant database by name.
func (s *ServicesService) getServiceID(serviceName, tenantID string) (string, error) {
db, err := config.GetTenantGORMDB(tenantID)
if err != nil {
return "", fmt.Errorf("failed to connect to tenant DB: %w", err)
}

var result struct {
ID string
}
err = db.Table("services").
Where("created_by = ? AND name = ?", tenantID, serviceName).
Order("created_at DESC").
Select("id").
First(&result).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return "", fmt.Errorf("service '%s' not found for tenant %s", serviceName, tenantID)
}
return "", fmt.Errorf("failed to query service: %w", err)
}
return result.ID, nil
}

// GetServiceCredentials validates a session and retrieves external service credentials.
// In-process replacement for the Python HTTP call to /exsvc/services/{id}/credentials.
func (s *ServicesService) GetServiceCredentials(sessionID, serviceName string) (map[string]interface{}, error) {
info, err := s.getSessionInfo(sessionID)
if err != nil {
return nil, err
}
if info.TenantID == "" {
return nil, fmt.Errorf("no tenant_id in session")
}
if info.AccessToken == "" {
return nil, fmt.Errorf("no access token in session")
}

serviceID, err := s.getServiceID(serviceName, info.TenantID)
if err != nil {
return nil, err
}

return s.fetchCredentials(serviceID, info.TenantID)
}

// fetchCredentials retrieves credentials for a service from the tenant DB.
func (s *ServicesService) fetchCredentials(serviceID, tenantID string) (map[string]interface{}, error) {
db, err := config.GetTenantGORMDB(tenantID)
if err != nil {
return nil, fmt.Errorf("failed to connect to tenant DB: %w", err)
}

var svc struct {
ID string `gorm:"column:id"`
Name string `gorm:"column:name"`
ServiceType string `gorm:"column:service_type"`
AuthType string `gorm:"column:auth_type"`
URL *string `gorm:"column:url"`
Credentials *string `gorm:"column:credentials"`
Metadata *string `gorm:"column:metadata"`
}

err = db.Table("external_services").
Where("id = ?", serviceID).
First(&svc).Error
if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, fmt.Errorf("external service %s not found", serviceID)
}
return nil, fmt.Errorf("failed to query external service: %w", err)
}
Comment on lines +264 to +287
type readResult struct {
line string
err error
}
ch := make(chan readResult, 1)
go func() {
line, err := info.stdout.ReadString('\n')
ch <- readResult{line, err}
}()

select {
case r := <-ch:
if r.err != nil {
return nil, r.err
}
var resp map[string]interface{}
if err := json.Unmarshal([]byte(r.line), &resp); err != nil {
return nil, fmt.Errorf("json: %w", err)
}
info.LastActivity = time.Now()
return resp, nil
case <-time.After(timeout):
return nil, fmt.Errorf("timeout")
}
Comment thread config/config.go
OAuthUserInfoURL string // OAuth userinfo endpoint
PKCEChallenge string // Pre-computed PKCE challenge (if static)
OAuthRedirectURI string // Default OAuth redirect URI
OAuthRedirectURITemplate string // Redirect URI template with {tenant_id}
Comment thread go.mod
Comment on lines 108 to 112
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sony/gobreaker/v2 v2.4.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
ritamAN77 pushed a commit that referenced this pull request Jul 9, 2026
#1 One-transaction grant: GrantAssignment now creates the connector
assignment + broker-RS registration (approved) + connector-executor role
binding on the client's service account in a single transaction
(repo.GrantAssignmentTx). RevokeAssignment tears down the registration +
binding only when it's the client's last assignment. Replaces the manual
4-table / raw-SQL enablement with one API call.

#2 last_seen_at: service_accounts.last_seen_at is now updated on M2M token
issuance and on a successful broker action (was NULL forever).

#3 Agent activity: GET /uflow/admin/agents/:id/activity returns the agent
lens over connector_action_audit (matched by actor_client_id / subject_id /
actor_spiffe_id) for Agent 360.

Build/vet/gofmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ritamAN77 pushed a commit that referenced this pull request Jul 10, 2026
…ng, refresh-lock, F8 audit

F7 (owner always): owner_email required at service-account creation (threaded
through CreateServiceAccount; implicit-create sites default to the acting
admin). Audit now records actor ALWAYS (act.client_id, else authenticating
client — fixes direct-M2M no-actor) and stamps the accountable owner into
every connector_action_audit row.

D6: reject a foreign-workspace client_id at grant time with a clear error
(resolveClientPrincipals now returns the SA workspace; GrantAssignmentTx
guards it). Cross-workspace stays the deferred A2A/XAA case.

#2a schema hardening — connector_connections: workspace_id + composite FK,
scope->binding_type, auth_type->auth_method, subject_user_id->uuid, binding/
status/auth CHECK constraints, NULL-safe partial unique indexes (a plain
UNIQUE let duplicate workspace rows through), and lifecycle cols (version,
external_account_*, refresh_expires_at, last_used_at, revoked_at).

#2b refresh-under-lock — Refresh takes a non-blocking pg advisory lock on the
connection id; a loser re-reads the rotated token instead of racing, the
winner does a version CAS and increments version. Kills the thundering-herd
refresh-token rotation race.

#2c F8 audit triad — split the overloaded outcome/http_status into
authz_outcome / broker_status / provider_status / action_outcome, so an
authorized-but-provider-failed call is no longer logged as success.

Schema change requires wipe+rebootstrap on deploy. Build/vet/gofmt clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ritamAN77 pushed a commit that referenced this pull request Jul 10, 2026
The broker now gates WHICH TEAM an agent may act FOR, not just which agent
calls — enforced inside the broker chain, not at the agent's own front door.

- connectors.allowed_subject_groups (uuid[]): group ids the on-behalf-of user
  must belong to; empty = no restriction.
- Gate 4 in runAction: for a delegated (XAA) call, the token subject must be a
  member of an allowed group (SubjectInAnyGroup over user_groups). A connector
  with a group policy but no human subject (M2M) is denied — the policy is
  meaningless without a subject.
- PUT /authsec/connectors/:id/subject-groups {group_ids} (connector:assign).

Closes Track A (design-review D5): #1 owner/D6, #2 schema-harden+refresh-lock+
F8, #3 GitHub App (F1), #4 input constraints (F3), #5 user consent (R4), #6
this. Additive column; rides the existing rebootstrap window. Build/vet/gofmt
clean; routes conflict-free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants