fix: environment variable not working on auth (#4829)

This commit is contained in:
Nivedin 2025-03-05 19:50:16 +05:30 committed by GitHub
parent ce99617aac
commit 6ce0fb8897
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -9,20 +9,22 @@ import {
type RelayV1,
type StatusCode,
body,
} from '@relay/v/1'
import type { VersionedAPI } from '@type/versioning'
} from "@relay/v/1"
import type { VersionedAPI } from "@type/versioning"
import { AwsV4Signer } from 'aws4fetch'
import axios, { AxiosRequestConfig } from 'axios'
import { AwsV4Signer } from "aws4fetch"
import axios, { AxiosRequestConfig } from "axios"
import * as E from 'fp-ts/Either'
import * as R from 'fp-ts/Record'
import { pipe } from 'fp-ts/function'
import * as E from "fp-ts/Either"
import * as R from "fp-ts/Record"
import { pipe } from "fp-ts/function"
const isStatusCode = (status: number): status is StatusCode =>
status >= 100 && status < 600
const normalizeHeaders = (headers: Record<string, any>): Record<string, string> =>
const normalizeHeaders = (
headers: Record<string, any>
): Record<string, string> =>
pipe(
headers,
R.filterWithIndex((_, v) => v !== undefined),
@ -32,39 +34,30 @@ const normalizeHeaders = (headers: Record<string, any>): Record<string, string>
export const implementation: VersionedAPI<RelayV1> = {
version: { major: 1, minor: 0, patch: 0 },
api: {
id: 'axios',
id: "axios",
capabilities: {
method: new Set([
'GET',
'POST',
'PUT',
'DELETE',
'PATCH',
'HEAD',
'OPTIONS'
]),
header: new Set([
'stringvalue',
'arrayvalue',
'multivalue'
"GET",
"POST",
"PUT",
"DELETE",
"PATCH",
"HEAD",
"OPTIONS",
]),
header: new Set(["stringvalue", "arrayvalue", "multivalue"]),
content: new Set([
'text',
'json',
'xml',
'form',
'urlencoded',
'compression'
]),
auth: new Set([
'basic',
'bearer',
'apikey',
'aws'
"text",
"json",
"xml",
"form",
"urlencoded",
"compression",
]),
auth: new Set(["basic", "bearer", "apikey", "aws"]),
security: new Set([]),
proxy: new Set([]),
advanced: new Set([])
advanced: new Set([]),
},
canHandle(request: RelayRequest) {
@ -73,16 +66,19 @@ export const implementation: VersionedAPI<RelayV1> = {
kind: "unsupported_feature",
feature: "method",
message: `Method ${request.method} is not supported`,
relay: "axios"
relay: "axios",
})
}
if (request.content && !this.capabilities.content.has(request.content.kind)) {
if (
request.content &&
!this.capabilities.content.has(request.content.kind)
) {
return E.left({
kind: "unsupported_feature",
feature: "content",
message: `Content type ${request.content.kind} is not supported`,
relay: "axios"
relay: "axios",
})
}
@ -91,7 +87,7 @@ export const implementation: VersionedAPI<RelayV1> = {
kind: "unsupported_feature",
feature: "authentication",
message: `Authentication type ${request.auth.kind} is not supported`,
relay: "axios"
relay: "axios",
})
}
@ -100,7 +96,7 @@ export const implementation: VersionedAPI<RelayV1> = {
kind: "unsupported_feature",
feature: "security",
message: "Client certificates are not supported",
relay: "axios"
relay: "axios",
})
}
@ -109,7 +105,7 @@ export const implementation: VersionedAPI<RelayV1> = {
kind: "unsupported_feature",
feature: "proxy",
message: "Proxy is not supported",
relay: "axios"
relay: "axios",
})
}
@ -121,150 +117,173 @@ export const implementation: VersionedAPI<RelayV1> = {
const emitter: RelayEventEmitter<RelayRequestEvents> = {
on: () => () => {},
once: () => () => {},
off: () => {}
off: () => {},
}
const response: Promise<E.Either<RelayError, RelayResponse>> = (async () => {
try {
const startTime = Date.now()
const config: AxiosRequestConfig = {
url: request.url,
method: request.method,
headers: request.headers,
params: request.params,
data: request.content?.content,
maxRedirects: request.meta?.options?.maxRedirects,
timeout: request.meta?.options?.timeout,
decompress: request.meta?.options?.decompress ?? true,
validateStatus: null,
cancelToken: cancelTokenSource.token,
responseType: 'arraybuffer'
}
if (request.auth) {
switch (request.auth.kind) {
case 'basic':
config.auth = {
username: request.auth.username,
password: request.auth.password
}
break
case 'bearer':
config.headers = {
...config.headers,
Authorization: `Bearer ${request.auth.token}`
}
break
case 'apikey':
if (request.auth.in === 'header') {
config.headers = {
...config.headers,
[request.auth.key]: request.auth.value
}
} else {
config.params = {
...config.params,
[request.auth.key]: request.auth.value
}
}
break
case 'aws': {
const { accessKey, secretKey, region, service, sessionToken, in: location } = request.auth
const signer = new AwsV4Signer({
url: request.url,
method: request.method,
accessKeyId: accessKey,
secretAccessKey: secretKey,
region,
service,
sessionToken,
datetime: new Date().toISOString().replace(/[:-]|\.\d{3}/g, ""),
signQuery: false
})
const signed = await signer.sign()
if (location === "query") {
config.url = signed.url.toString()
} else {
const headers: Record<string, string> = {}
signed.headers.forEach((value, key) => {
headers[key] = value
})
config.headers = {
...config.headers,
...headers
}
}
break
}
const response: Promise<E.Either<RelayError, RelayResponse>> =
(async () => {
try {
const startTime = Date.now()
const config: AxiosRequestConfig = {
url: request.url,
method: request.method,
headers: request.headers,
params: request.params,
data: request.content?.content,
maxRedirects: request.meta?.options?.maxRedirects,
timeout: request.meta?.options?.timeout,
decompress: request.meta?.options?.decompress ?? true,
validateStatus: null,
cancelToken: cancelTokenSource.token,
responseType: "arraybuffer",
}
}
const axiosResponse = await axios(config)
const endTime = Date.now()
// The following code is temporarily commented out because the auth has been pre-processed in EffectiveURL.ts and added in header
// and preprocessing here will cause the environment variables not parsed since the auth object only has the raw value
if (!isStatusCode(axiosResponse.status)) {
return E.left({
kind: 'version',
message: `Invalid status code: ${axiosResponse.status}`
})
}
// if (request.auth) {
// switch (request.auth.kind) {
// case "basic":
// config.auth = {
// username: request.auth.username,
// password: request.auth.password,
// }
// break
// case "bearer":
// config.headers = {
// ...config.headers,
// Authorization: `Bearer ${request.auth.token}`,
// }
// break
// case "apikey":
// if (request.auth.in === "header") {
// config.headers = {
// ...config.headers,
// [request.auth.key]: request.auth.value,
// }
// } else {
// config.params = {
// ...config.params,
// [request.auth.key]: request.auth.value,
// }
// }
// break
// case "aws": {
// const {
// accessKey,
// secretKey,
// region,
// service,
// sessionToken,
// in: location,
// } = request.auth
// const signer = new AwsV4Signer({
// url: request.url,
// method: request.method,
// accessKeyId: accessKey,
// secretAccessKey: secretKey,
// region,
// service,
// sessionToken,
// datetime: new Date()
// .toISOString()
// .replace(/[:-]|\.\d{3}/g, ""),
// signQuery: false,
// })
// const signed = await signer.sign()
// if (location === "query") {
// config.url = signed.url.toString()
// } else {
// const headers: Record<string, string> = {}
// signed.headers.forEach((value, key) => {
// headers[key] = value
// })
// config.headers = {
// ...config.headers,
// ...headers,
// }
// }
// break
// }
// }
// }
const normalizedHeaders = normalizeHeaders(axiosResponse.headers)
const contentType = normalizedHeaders['content-type'] || normalizedHeaders['Content-Type'] || normalizedHeaders['CONTENT-TYPE']
const axiosResponse = await axios(config)
const endTime = Date.now()
const response: RelayResponse = {
id: request.id,
status: axiosResponse.status,
statusText: axiosResponse.statusText,
version: request.version,
headers: normalizedHeaders,
body: body.body(axiosResponse.data, contentType),
meta: {
timing: {
start: startTime,
end: endTime,
},
size: {
headers: JSON.stringify(axiosResponse.headers).length,
body: axiosResponse.data?.length ?? 0,
total: JSON.stringify(axiosResponse.headers).length + (axiosResponse.data?.length ?? 0)
}
}
}
return E.right(response)
} catch (error) {
if (axios.isCancel(error)) {
return E.left({ kind: 'abort', message: 'Request cancelled' })
}
if (axios.isAxiosError(error)) {
if (error.code === 'ECONNABORTED') {
if (!isStatusCode(axiosResponse.status)) {
return E.left({
kind: 'timeout',
message: 'Request timed out',
phase: 'response'
kind: "version",
message: `Invalid status code: ${axiosResponse.status}`,
})
}
const normalizedHeaders = normalizeHeaders(axiosResponse.headers)
const contentType =
normalizedHeaders["content-type"] ||
normalizedHeaders["Content-Type"] ||
normalizedHeaders["CONTENT-TYPE"]
const response: RelayResponse = {
id: request.id,
status: axiosResponse.status,
statusText: axiosResponse.statusText,
version: request.version,
headers: normalizedHeaders,
body: body.body(axiosResponse.data, contentType),
meta: {
timing: {
start: startTime,
end: endTime,
},
size: {
headers: JSON.stringify(axiosResponse.headers).length,
body: axiosResponse.data?.length ?? 0,
total:
JSON.stringify(axiosResponse.headers).length +
(axiosResponse.data?.length ?? 0),
},
},
}
return E.right(response)
} catch (error) {
if (axios.isCancel(error)) {
return E.left({ kind: "abort", message: "Request cancelled" })
}
if (axios.isAxiosError(error)) {
if (error.code === "ECONNABORTED") {
return E.left({
kind: "timeout",
message: "Request timed out",
phase: "response",
})
}
return E.left({
kind: "network",
message: error.message,
})
}
return E.left({
kind: 'network',
message: error.message
kind: "network",
message:
error instanceof Error
? error.message
: "Unknown error occurred",
cause: error,
})
}
return E.left({
kind: 'network',
message: error instanceof Error ? error.message : 'Unknown error occurred',
cause: error
})
}
})()
})()
return {
cancel: async () => { cancelTokenSource.cancel() },
cancel: async () => {
cancelTokenSource.cancel()
},
emitter,
response
response,
}
}
}
},
},
}