package azure import ( "context" "fmt" "time" "github.com/Azure/azure-sdk-for-go/sdk/azcore/policy" "github.com/maximhq/bifrost/core/providers/openai" providerUtils "github.com/maximhq/bifrost/core/providers/utils" "github.com/maximhq/bifrost/core/schemas" "github.com/valyala/fasthttp" ) // setAzureAuth sets the Azure authentication header on the request for OpenAI models. // It handles authentication in order of priority: // 1. Service Principal (client ID/secret/tenant ID) - uses Bearer token // 2. Context token - uses Bearer token // 3. API key - uses api-key header // 4. DefaultAzureCredential auto-detection (managed identity, workload identity, env vars, CLI) func (provider *AzureProvider) setAzureAuth(ctx context.Context, req *fasthttp.Request, key schemas.Key) *schemas.BifrostError { // Service Principal authentication if key.AzureKeyConfig != nil && key.AzureKeyConfig.ClientID != nil && key.AzureKeyConfig.ClientSecret != nil && key.AzureKeyConfig.TenantID != nil && key.AzureKeyConfig.ClientID.GetValue() != "" && key.AzureKeyConfig.ClientSecret.GetValue() != "" && key.AzureKeyConfig.TenantID.GetValue() != "" { cred, err := provider.getOrCreateAuth(key.AzureKeyConfig.TenantID.GetValue(), key.AzureKeyConfig.ClientID.GetValue(), key.AzureKeyConfig.ClientSecret.GetValue()) if err != nil { return providerUtils.NewBifrostOperationError("failed to get or create Azure authentication", err) } scopes := getAzureScopes(key.AzureKeyConfig.Scopes) token, err := cred.GetToken(ctx, policy.TokenRequestOptions{ Scopes: scopes, }) if err != nil { return providerUtils.NewBifrostOperationError("failed to get Azure access token", err) } if token.Token == "" { return providerUtils.NewBifrostOperationError("azure access token is empty", fmt.Errorf("token is empty")) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.Token)) req.Header.Del("api-key") return nil } // Context token authentication if authToken, ok := ctx.Value(AzureAuthorizationTokenKey).(string); ok && authToken != "" { req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", authToken)) req.Header.Del("api-key") return nil } // API key authentication value := key.Value.GetValue() if value != "" { req.Header.Del("Authorization") req.Header.Set("api-key", value) return nil } // No explicit credentials - attempt DefaultAzureCredential auto-detection. scopes := getAzureScopes(nil) if key.AzureKeyConfig != nil { scopes = getAzureScopes(key.AzureKeyConfig.Scopes) } cred, err := provider.getOrCreateDefaultAzureCredential() if err != nil { return providerUtils.NewBifrostOperationError("no credentials provided and DefaultAzureCredential unavailable", err) } token, err := cred.GetToken(ctx, policy.TokenRequestOptions{Scopes: scopes}) if err != nil { return providerUtils.NewBifrostOperationError("no credentials provided and DefaultAzureCredential failed to get token", err) } if token.Token == "" { return providerUtils.NewBifrostOperationError("no credentials provided and DefaultAzureCredential returned empty token", fmt.Errorf("token is empty")) } req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token.Token)) req.Header.Del("api-key") return nil } // AzureFileResponse represents an Azure file response (same as OpenAI). type AzureFileResponse struct { ID string `json:"id"` Object string `json:"object"` Bytes int64 `json:"bytes"` CreatedAt int64 `json:"created_at"` Filename string `json:"filename"` Purpose schemas.FilePurpose `json:"purpose"` Status string `json:"status,omitempty"` StatusDetails *string `json:"status_details,omitempty"` } // ToBifrostFileUploadResponse converts Azure file response to Bifrost response. func (r *AzureFileResponse) ToBifrostFileUploadResponse(providerName schemas.ModelProvider, latency time.Duration, sendBackRawResponse bool, rawResponse interface{}) *schemas.BifrostFileUploadResponse { resp := &schemas.BifrostFileUploadResponse{ ID: r.ID, Object: r.Object, Bytes: r.Bytes, CreatedAt: r.CreatedAt, Filename: r.Filename, Purpose: r.Purpose, Status: openai.ToBifrostFileStatus(r.Status), StatusDetails: r.StatusDetails, StorageBackend: schemas.FileStorageAPI, ExtraFields: schemas.BifrostResponseExtraFields{ Latency: latency.Milliseconds(), }, } if sendBackRawResponse { resp.ExtraFields.RawResponse = rawResponse } return resp }