feat: CORS enable button...ish still now understanding this
This commit is contained in:
@@ -10,8 +10,13 @@ import {
|
||||
GetResourcesCommand,
|
||||
GetStagesCommand,
|
||||
PutIntegrationCommand,
|
||||
PutIntegrationResponseCommand,
|
||||
PutMethodCommand,
|
||||
PutMethodResponseCommand,
|
||||
type APIGatewayClient,
|
||||
type Integration,
|
||||
type IntegrationResponse,
|
||||
type MethodResponse,
|
||||
type RestApi,
|
||||
} from '@aws-sdk/client-api-gateway'
|
||||
import {AddPermissionCommand, GetFunctionCommand, RemovePermissionCommand, type LambdaClient} from '@aws-sdk/client-lambda'
|
||||
@@ -19,7 +24,7 @@ import {apiGateway as defaultApiGateway, defaultAccountId, lambda as defaultLamb
|
||||
import {awsApiGatewaySchema} from '../cloud-spi/apigatewaySchema'
|
||||
import {ValidationError} from '../cloud-spi/errors'
|
||||
import {awsEndpoint} from '../service/runtimeProbe'
|
||||
import type {ApiGatewayDeleteRouteInput, ApiGatewayLambdaRouteInput, ApiGatewayLambdaRouteResult, ApiGatewayRouteOverview, ApiGatewayRoutesResult, ApiGatewayTestRouteInput, ApiGatewayTestRouteResult, CloudResource, CloudServiceAdapter, CreateResourceInput, ResourceQuery, ServiceSchema} from '../cloud-spi/types'
|
||||
import type {ApiGatewayCorsInput, ApiGatewayCorsResult, ApiGatewayDeleteRouteInput, ApiGatewayLambdaRouteInput, ApiGatewayLambdaRouteResult, ApiGatewayRouteOverview, ApiGatewayRoutesResult, ApiGatewayTestRouteInput, ApiGatewayTestRouteResult, CloudResource, CloudServiceAdapter, CreateResourceInput, ResourceQuery, ServiceSchema} from '../cloud-spi/types'
|
||||
|
||||
export class AwsApiGatewayAdapter implements CloudServiceAdapter {
|
||||
readonly cloud = 'aws' as const
|
||||
@@ -200,6 +205,7 @@ export class AwsApiGatewayAdapter implements CloudServiceAdapter {
|
||||
path: resource.path,
|
||||
method,
|
||||
functionName: lambdaFunctionName(result.methodIntegration?.uri),
|
||||
integrationType: result.methodIntegration?.type ?? null,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -245,6 +251,93 @@ export class AwsApiGatewayAdapter implements CloudServiceAdapter {
|
||||
}))
|
||||
}
|
||||
|
||||
async enableApiGatewayCors(apiId: string, input: ApiGatewayCorsInput): Promise<ApiGatewayCorsResult> {
|
||||
const resourceId = stringValue(input.resourceId)
|
||||
const stage = stageName(input.stage)
|
||||
const origin = stringValue(input.origin)
|
||||
const headers = stringValue(input.headers)
|
||||
const methods = [...new Set(input.methods.map((method) => method.toUpperCase()))]
|
||||
if (!resourceId) throw new ValidationError('resourceId is required')
|
||||
if (!origin) throw new ValidationError('origin is required')
|
||||
if (!headers) throw new ValidationError('headers are required')
|
||||
if (methods.length === 0 || methods.some((method) => !CORS_METHODS.includes(method))) {
|
||||
throw new ValidationError('methods must contain one or more of GET, POST, PUT, DELETE, or OPTIONS')
|
||||
}
|
||||
|
||||
const corsParameters = corsResponseParameters(origin, headers, methods)
|
||||
const configuredMethods: string[] = []
|
||||
const proxyMethods: string[] = []
|
||||
for (const method of methods) {
|
||||
if (method === 'OPTIONS') continue
|
||||
const route = await this.apiGateway.send(new GetMethodCommand({restApiId: apiId, resourceId, httpMethod: method}))
|
||||
if (route.methodIntegration?.type === 'AWS_PROXY') {
|
||||
proxyMethods.push(method)
|
||||
continue
|
||||
}
|
||||
const responseCodes = Object.keys(route.methodResponses ?? {200: {}})
|
||||
for (const statusCode of responseCodes) {
|
||||
await this.apiGateway.send(new PutMethodResponseCommand({
|
||||
restApiId: apiId, resourceId, httpMethod: method, statusCode,
|
||||
responseParameters: {
|
||||
...route.methodResponses?.[statusCode]?.responseParameters,
|
||||
...corsMethodResponseParameters(),
|
||||
},
|
||||
responseModels: route.methodResponses?.[statusCode]?.responseModels,
|
||||
}))
|
||||
}
|
||||
const integrationResponses = route.methodIntegration?.integrationResponses ?? {200: {}}
|
||||
for (const [statusCode, response] of Object.entries(integrationResponses)) {
|
||||
await this.apiGateway.send(new PutIntegrationResponseCommand({
|
||||
restApiId: apiId, resourceId, httpMethod: method, statusCode,
|
||||
selectionPattern: response.selectionPattern,
|
||||
responseParameters: {...response.responseParameters, ...corsParameters},
|
||||
responseTemplates: response.responseTemplates,
|
||||
contentHandling: response.contentHandling,
|
||||
}))
|
||||
}
|
||||
configuredMethods.push(method)
|
||||
}
|
||||
|
||||
let existingOptionsMethodResponse: MethodResponse | undefined
|
||||
let existingOptionsIntegration: Integration | undefined
|
||||
let existingOptionsIntegrationResponse: IntegrationResponse | undefined
|
||||
try {
|
||||
const optionsRoute = await this.apiGateway.send(new GetMethodCommand({
|
||||
restApiId: apiId, resourceId, httpMethod: 'OPTIONS',
|
||||
}))
|
||||
existingOptionsMethodResponse = optionsRoute.methodResponses?.['200']
|
||||
existingOptionsIntegration = optionsRoute.methodIntegration
|
||||
existingOptionsIntegrationResponse = optionsRoute.methodIntegration?.integrationResponses?.['200']
|
||||
} catch (error) {
|
||||
if (!hasHttpStatus(error, 404)) throw error
|
||||
}
|
||||
await this.apiGateway.send(new PutMethodCommand({restApiId: apiId, resourceId, httpMethod: 'OPTIONS', authorizationType: 'NONE'}))
|
||||
await this.apiGateway.send(new PutMethodResponseCommand({
|
||||
restApiId: apiId, resourceId, httpMethod: 'OPTIONS', statusCode: '200',
|
||||
responseParameters: {
|
||||
...corsMethodResponseParameters(),
|
||||
...existingOptionsMethodResponse?.responseParameters,
|
||||
},
|
||||
responseModels: existingOptionsMethodResponse?.responseModels,
|
||||
}))
|
||||
await this.apiGateway.send(new PutIntegrationCommand({
|
||||
restApiId: apiId, resourceId, httpMethod: 'OPTIONS', type: 'MOCK',
|
||||
requestTemplates: {'application/json': '{"statusCode": 200}'},
|
||||
timeoutInMillis: existingOptionsIntegration?.timeoutInMillis,
|
||||
}))
|
||||
await this.apiGateway.send(new PutIntegrationResponseCommand({
|
||||
restApiId: apiId, resourceId, httpMethod: 'OPTIONS', statusCode: '200',
|
||||
responseParameters: corsParameters,
|
||||
responseTemplates: existingOptionsIntegrationResponse?.responseTemplates,
|
||||
selectionPattern: existingOptionsIntegrationResponse?.selectionPattern,
|
||||
contentHandling: existingOptionsIntegrationResponse?.contentHandling,
|
||||
}))
|
||||
await this.apiGateway.send(new CreateDeploymentCommand({
|
||||
restApiId: apiId, stageName: stage, description: `Enable CORS for resource ${resourceId}`,
|
||||
}))
|
||||
return {configuredMethods, proxyMethods}
|
||||
}
|
||||
|
||||
private async rootResource(apiId: string) {
|
||||
let position: string | undefined
|
||||
do {
|
||||
@@ -277,6 +370,26 @@ export class AwsApiGatewayAdapter implements CloudServiceAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
const CORS_METHODS = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
|
||||
|
||||
function corsMethodResponseParameters(): Record<string, boolean> {
|
||||
return {
|
||||
'method.response.header.Access-Control-Allow-Origin': false,
|
||||
'method.response.header.Access-Control-Allow-Headers': false,
|
||||
'method.response.header.Access-Control-Allow-Methods': false,
|
||||
}
|
||||
}
|
||||
|
||||
function corsResponseParameters(origin: string, headers: string, methods: string[]): Record<string, string> {
|
||||
const requestedMethods = new Set([...methods, 'OPTIONS'])
|
||||
const allowedMethods = CORS_METHODS.filter((method) => requestedMethods.has(method))
|
||||
return {
|
||||
'method.response.header.Access-Control-Allow-Origin': `'${origin}'`,
|
||||
'method.response.header.Access-Control-Allow-Headers': `'${headers}'`,
|
||||
'method.response.header.Access-Control-Allow-Methods': `'${allowedMethods.join(',')}'`,
|
||||
}
|
||||
}
|
||||
|
||||
function routePathPart(path: string): string {
|
||||
const value = stringValue(path).replace(/^\//, '')
|
||||
if (!/^[A-Za-z0-9._~-]+$/.test(value)) {
|
||||
|
||||
Reference in New Issue
Block a user