feat: CORS enable button...ish still now understanding this

This commit is contained in:
Zakaria
2026-09-27 14:46:04 -04:00
parent 8470b8d17a
commit b474e823ce
17 changed files with 943 additions and 31 deletions
+55
View File
@@ -145,6 +145,61 @@ describe('EventBridge rule routes', () => {
})
})
describe('API Gateway CORS routes', () => {
test('dispatches resource-scoped CORS configuration through the API Gateway adapter', async () => {
const calls: unknown[] = []
const app = appWithRoutes([mockAdapter('aws', {
service: 'apigateway',
enableApiGatewayCors: async (apiId, input) => {
calls.push({apiId, input})
return {configuredMethods: ['GET'], proxyMethods: ['POST']}
},
})])
const response = await app.request('/api/clouds/aws/services/apigateway/resources/api-123/cors', {
method: 'POST', headers: {'content-type': 'application/json'},
body: JSON.stringify({resourceId: 'resource-456', stage: 'dev', origin: '*', headers: 'Content-Type', methods: ['GET', 'POST']}),
})
expect(response.status).toBe(200)
expect(await response.json()).toEqual({configuredMethods: ['GET'], proxyMethods: ['POST']})
expect(calls).toEqual([{apiId: 'api-123', input: {
resourceId: 'resource-456', stage: 'dev', origin: '*', headers: 'Content-Type', methods: ['GET', 'POST'],
}}])
})
})
describe('S3 website routes', () => {
test('routes normalized website configuration lifecycle requests through the storage adapter', async () => {
const calls: Array<{operation: string; args: unknown[]}> = []
const adapter = mockAdapter('aws', {
getS3BucketWebsite: async (...args) => {
calls.push({operation: 'get', args})
return {mode: 'index', indexDocument: 'index.html', errorDocument: null}
},
putS3BucketWebsite: async (...args) => { calls.push({operation: 'put', args}) },
deleteS3BucketWebsite: async (...args) => { calls.push({operation: 'delete', args}) },
})
const app = appWithRoutes([adapter])
const get = await app.request('/api/clouds/aws/services/storage/resources/assets/website')
const put = await app.request('/api/clouds/aws/services/storage/resources/assets/website', {
method: 'PUT', headers: {'content-type': 'application/json'},
body: JSON.stringify({mode: 'redirect', hostName: 'www.example.com', protocol: 'https'}),
})
const remove = await app.request('/api/clouds/aws/services/storage/resources/assets/website', {method: 'DELETE'})
expect(await get.json()).toEqual({mode: 'index', indexDocument: 'index.html', errorDocument: null})
expect(put.status).toBe(200)
expect(remove.status).toBe(200)
expect(calls).toEqual([
{operation: 'get', args: ['assets']},
{operation: 'put', args: ['assets', {mode: 'redirect', hostName: 'www.example.com', protocol: 'https'}]},
{operation: 'delete', args: ['assets']},
])
})
})
describe('SNS subscription routes', () => {
test('routes subscription lifecycle requests through the Notifications adapter', async () => {
const calls: Array<{operation: string; args: unknown[]}> = []
+73
View File
@@ -5,6 +5,7 @@ import type {
CloudServiceType,
CreateDatabaseSnapshotInput,
KmsEncryptionAlgorithm,
S3BucketWebsiteConfiguration,
SqlConnectionInput,
} from '../cloud-spi/types'
import {clampLimit, type PageQuery} from '../cloud-spi/childCollections'
@@ -554,6 +555,22 @@ export function createCloudRoutes(injectedService?: CloudProxyService) {
})
})
app.post('/:cloud/services/apigateway/resources/:id/cors', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const result = await svc(c).enableApiGatewayCors(cloud, c.req.param('id'), {
resourceId: typeof input.resourceId === 'string' ? input.resourceId : '',
stage: typeof input.stage === 'string' ? input.stage : '',
origin: typeof input.origin === 'string' ? input.origin : '',
headers: typeof input.headers === 'string' ? input.headers : '',
methods: Array.isArray(input.methods) ? input.methods.filter((method): method is string => typeof method === 'string') : [],
})
return c.json(result)
})
})
app.post('/:cloud/services/apigateway/resources/:id/routes/test', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
@@ -602,6 +619,32 @@ export function createCloudRoutes(injectedService?: CloudProxyService) {
})
})
app.get('/:cloud/services/storage/resources/:id/website', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).getS3BucketWebsite(cloud, c.req.param('id'))))
})
app.put('/:cloud/services/storage/resources/:id/website', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await jsonBody<{mode?: unknown; indexDocument?: unknown; errorDocument?: unknown; hostName?: unknown; protocol?: unknown}>(c)
const configuration = s3WebsiteConfiguration(input)
await svc(c).putS3BucketWebsite(cloud, c.req.param('id'), configuration)
return c.json({ok: true})
})
})
app.delete('/:cloud/services/storage/resources/:id/website', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteS3BucketWebsite(cloud, c.req.param('id'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/serverless/resources/:id/eventbridge-triggers', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
@@ -1052,6 +1095,36 @@ function isCloudProvider(value: string): value is CloudProvider {
return value === 'aws' || value === 'azure' || value === 'gcp'
}
function s3WebsiteConfiguration(input: {mode?: unknown; indexDocument?: unknown; errorDocument?: unknown; hostName?: unknown; protocol?: unknown}): S3BucketWebsiteConfiguration {
if (input.mode === 'index') {
if (typeof input.indexDocument !== 'string' || !input.indexDocument.trim()) {
throw new ValidationError('indexDocument is required for index website hosting')
}
if (input.errorDocument !== undefined && typeof input.errorDocument !== 'string' && input.errorDocument !== null) {
throw new ValidationError('errorDocument must be a string or null')
}
return {
mode: 'index',
indexDocument: input.indexDocument.trim(),
errorDocument: typeof input.errorDocument === 'string' && input.errorDocument.trim() ? input.errorDocument.trim() : null,
}
}
if (input.mode === 'redirect') {
if (typeof input.hostName !== 'string' || !input.hostName.trim()) {
throw new ValidationError('hostName is required for redirect website hosting')
}
if (input.protocol !== undefined && input.protocol !== null && input.protocol !== 'http' && input.protocol !== 'https') {
throw new ValidationError('protocol must be http, https, or null')
}
return {
mode: 'redirect',
hostName: input.hostName.trim(),
protocol: input.protocol === 'http' || input.protocol === 'https' ? input.protocol : null,
}
}
throw new ValidationError('mode must be index or redirect')
}
async function jsonBody<T>(c: Context): Promise<T> {
try {
const body = await c.req.json<unknown>()