fix: environment variable not working on auth (#4829)
This commit is contained in:
parent
ce99617aac
commit
6ce0fb8897
1 changed files with 188 additions and 169 deletions
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue