(params: VantaTokenParams, cacheKey: string)
| 174 | } |
| 175 | |
| 176 | async function exchangeVantaToken(params: VantaTokenParams, cacheKey: string): Promise<string> { |
| 177 | const response = await fetch(`${getVantaBaseUrl(params.region)}/oauth/token`, { |
| 178 | method: 'POST', |
| 179 | headers: { |
| 180 | Accept: 'application/json', |
| 181 | 'Content-Type': 'application/json', |
| 182 | }, |
| 183 | body: JSON.stringify({ |
| 184 | client_id: params.clientId, |
| 185 | client_secret: params.clientSecret, |
| 186 | scope: params.scope, |
| 187 | grant_type: 'client_credentials', |
| 188 | }), |
| 189 | cache: 'no-store', |
| 190 | signal: AbortSignal.timeout(VANTA_TOKEN_EXCHANGE_TIMEOUT_MS), |
| 191 | }) |
| 192 | |
| 193 | const data: unknown = await response.json().catch(() => null) |
| 194 | if (!response.ok) { |
| 195 | throw new Error(extractVantaError(data, 'Failed to authenticate with Vanta')) |
| 196 | } |
| 197 | |
| 198 | if (!isRecordLike(data) || typeof data.access_token !== 'string') { |
| 199 | throw new Error('Vanta authentication did not return an access token') |
| 200 | } |
| 201 | |
| 202 | const expiresInMs = (getNumber(data.expires_in) ?? 0) * 1000 |
| 203 | if (expiresInMs > VANTA_TOKEN_EXPIRY_BUFFER_MS) { |
| 204 | vantaTokenCache.set(cacheKey, { |
| 205 | token: data.access_token, |
| 206 | expiresAt: Date.now() + expiresInMs - VANTA_TOKEN_EXPIRY_BUFFER_MS, |
| 207 | }) |
| 208 | } |
| 209 | |
| 210 | return data.access_token |
| 211 | } |
| 212 | |
| 213 | /** |
| 214 | * Returns a bearer token for the Vanta API, exchanging OAuth client |
no test coverage detected