import { awsSignedFetch } from './client.ts'
import { mutationPreview } from './helpers.ts'
import { redactUnknown, looksLikeCredentialText } from './redact.ts'
import {
assertService,
inputRecord,
optionalString,
requiredString,
type AwsAuthOptions,
} from './validation.ts'
const WRITE_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])
/**
* Escape hatch for a generic SigV4-signed AWS REST call.
*
* Writes default to dry-run and require `confirm: true`. Responses never
* include object bodies that look like credentials.
*
* @example
* import { awsRequest } from 'kody:@kody/aws/request'
* const result = await awsRequest({
* service: 'sts',
* method: 'POST',
* path: '/',
* body: 'Action=GetCallerIdentity&Version=2011-06-15',
* })
*/
export async function awsRequest(
params: AwsAuthOptions & {
service: string
method?: string
path?: string
host?: string
headers?: Record<string, string>
query?: Record<string, string>
body?: string
dryRun?: boolean
confirm?: boolean
},
) {
const input = inputRecord(params)
const service = assertService(requiredString(input, 'service'))
const method = (optionalString(input, 'method') ?? 'GET').toUpperCase()
const path = optionalString(input, 'path') ?? '/'
const host = optionalString(input, 'host')
const body = optionalString(input, 'body')
const mutating = WRITE_METHODS.has(method)
if (mutating) {
const preview = mutationPreview(input, {
method,
service,
region: optionalString(input, 'region') ?? 'us-east-1',
host: host ?? service + '.amazonaws.com',
path,
body: body ? { bodyLength: body.length } : null,
})
if (preview) return preview
}
const result = await awsSignedFetch({
...params,
service,
method,
path,
host,
headers: (input.headers as Record<string, string> | undefined) ?? {},
query: (input.query as Record<string, string> | undefined) ?? {},
body,
})
const looksJson = result.text.trim().startsWith('{')
const looksXml = result.text.trim().startsWith('<')
let parsed: unknown = null
if (looksJson) {
try {
parsed = redactUnknown(JSON.parse(result.text))
} catch {
parsed = null
}
}
const credentialLike = looksLikeCredentialText(result.text)
return {
ok: true,
status: result.status,
service,
region: result.region,
host: result.host,
method,
path,
body:
credentialLike || (!looksJson && !looksXml)
? null
: parsed ?? redactUnknown(result.text.slice(0, 4000)),
bodyOmitted: credentialLike || (!looksJson && !looksXml && result.text.length > 0),
}
}
export default awsRequest