github.com/google/osv-scalibr@v0.4.1/veles/secrets/grokxaiapikey/validator.go (about) 1 // Copyright 2025 Google LLC 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Copyright 2025 Google LLC 16 // 17 // Licensed under the Apache License, Version 2.0 (the "License"); 18 // you may not use this file except in compliance with the License. 19 // You may obtain a copy of the License at 20 // 21 // http://www.apache.org/licenses/LICENSE-2.0 22 // 23 // Unless required by applicable law or agreed to in writing, 24 // distributed under the License is distributed on an "AS IS" BASIS, 25 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 // See the License for the specific language governing permissions and 27 // limitations under the License. 28 29 package grokxaiapikey 30 31 import ( 32 "encoding/json" 33 "fmt" 34 "io" 35 "net/http" 36 "time" 37 38 "github.com/google/osv-scalibr/veles" 39 "github.com/google/osv-scalibr/veles/secrets/common/simplevalidate" 40 ) 41 42 const ( 43 // Used to validate standard API keys directly by checking blocked/disabled flags. 44 apiEndpoint = "https://api.x.ai/v1/api-key" 45 // Uses a dummy teamId since every management API request requires {teamId}. 46 // This forces predictable error patterns that allow indirect validation of management keys. 47 managementEndpoint = "https://management-api.x.ai/auth/teams/ffffffff-ffff-ffff-ffff-ffffffffffff/api-keys" 48 // validationTimeout is timeout for API validation requests. 49 validationTimeout = 10 * time.Second 50 ) 51 52 // 53 // --- Grok XAI API Key Validator --- 54 // 55 56 // apiKeyResponse represents the JSON returned by the x.ai API key endpoint. 57 type apiKeyResponse struct { 58 APIKeyBlocked bool `json:"api_key_blocked"` 59 APIKeyDisabled bool `json:"api_key_disabled"` 60 } 61 62 // apiKeyStatusFromBody verifies responses from /v1/api-key. 63 // If either api_key_blocked or api_key_disabled is true, the key is invalid. 64 func apiKeyStatusFromBody(body io.Reader) (veles.ValidationStatus, error) { 65 bodyBytes, err := io.ReadAll(body) 66 if err != nil { 67 return veles.ValidationFailed, fmt.Errorf("failed to read response body: %w", err) 68 } 69 var resp apiKeyResponse 70 if err := json.Unmarshal(bodyBytes, &resp); err != nil { 71 return veles.ValidationFailed, fmt.Errorf("failed to parse JSON response: %w", err) 72 } 73 74 if resp.APIKeyBlocked || resp.APIKeyDisabled { 75 return veles.ValidationInvalid, nil 76 } 77 return veles.ValidationValid, nil 78 } 79 80 // NewAPIValidator creates a new Validator for GrokXAIAPIKey. 81 func NewAPIValidator() *simplevalidate.Validator[GrokXAIAPIKey] { 82 return &simplevalidate.Validator[GrokXAIAPIKey]{ 83 Endpoint: apiEndpoint, 84 HTTPMethod: http.MethodGet, 85 HTTPHeaders: func(k GrokXAIAPIKey) map[string]string { 86 return map[string]string{ 87 "Authorization": "Bearer " + k.Key, 88 } 89 }, 90 StatusFromResponseBody: apiKeyStatusFromBody, 91 HTTPC: &http.Client{ 92 Timeout: validationTimeout, 93 }, 94 } 95 } 96 97 // 98 // --- Grok XAI Management Key Validator --- 99 // 100 101 // managementErrorResponse represents the JSON returned when a management key is checked. 102 type managementErrorResponse struct { 103 Code int `json:"code"` 104 Message string `json:"message"` 105 } 106 107 func managementKeyStatusFromBody(body io.Reader) (veles.ValidationStatus, error) { 108 bodyBytes, err := io.ReadAll(body) 109 if err != nil { 110 return veles.ValidationFailed, fmt.Errorf("failed to read response body: %w", err) 111 } 112 113 var resp managementErrorResponse 114 if err := json.Unmarshal(bodyBytes, &resp); err != nil { 115 return veles.ValidationFailed, fmt.Errorf("unable to parse response from %q: %w", managementEndpoint, err) 116 } 117 if resp.Code == 7 { 118 // Team mismatch error → means the key itself is valid. 119 // Every management API call requires a {teamId}, but we don't know the real one. 120 // By using a fake teamId, a valid key passes authentication but fails authorization, 121 // producing code 7 ("team mismatch"). This reliably distinguishes valid keys from invalid ones. 122 return veles.ValidationValid, nil 123 } 124 // Other 403 codes → the key was authenticated but failed authorization for reasons other than team mismatch. 125 // This indicates the key is not valid for use. 126 return veles.ValidationInvalid, nil 127 } 128 129 // NewManagementAPIValidator creates a new Validator for GrokXAIManagementKey. 130 func NewManagementAPIValidator() *simplevalidate.Validator[GrokXAIManagementKey] { 131 return &simplevalidate.Validator[GrokXAIManagementKey]{ 132 Endpoint: managementEndpoint, 133 HTTPMethod: http.MethodGet, 134 HTTPHeaders: func(k GrokXAIManagementKey) map[string]string { 135 return map[string]string{ 136 "Authorization": "Bearer " + k.Key, 137 } 138 }, 139 ValidResponseCodes: []int{http.StatusOK}, 140 InvalidResponseCodes: []int{http.StatusUnauthorized}, 141 StatusFromResponseBody: managementKeyStatusFromBody, 142 HTTPC: &http.Client{ 143 Timeout: validationTimeout, 144 }, 145 } 146 }