@gugananuvem/aws-local-simulator 1.0.35 → 1.0.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/README.md +662 -658
  2. package/package.json +10 -3
  3. package/src/config/default-config.js +71 -0
  4. package/src/config/env-loader.js +4 -2
  5. package/src/middleware/authorization.js +271 -0
  6. package/src/middleware/cloudtrail-logger.js +333 -0
  7. package/src/middleware/error-handler.js +138 -0
  8. package/src/middleware/index.js +79 -0
  9. package/src/middleware/policy-enforcement.js +407 -0
  10. package/src/server.js +74 -4
  11. package/src/services/acm/index.js +66 -0
  12. package/src/services/acm/models/certificate.js +202 -0
  13. package/src/services/acm/server.js +224 -0
  14. package/src/services/acm/simulator.js +293 -0
  15. package/src/services/alb/index.js +104 -0
  16. package/src/services/alb/models/loadBalancer.js +88 -0
  17. package/src/services/alb/models/targetGroup.js +128 -0
  18. package/src/services/alb/server.js +160 -0
  19. package/src/services/alb/simulator.js +262 -0
  20. package/src/services/apigateway/index.js +22 -0
  21. package/src/services/apigateway/server.js +247 -35
  22. package/src/services/apigateway/simulator.js +780 -26
  23. package/src/services/asg/index.js +100 -0
  24. package/src/services/asg/models/autoScalingGroup.js +110 -0
  25. package/src/services/asg/models/launchTemplate.js +41 -0
  26. package/src/services/asg/models/scalingPolicy.js +51 -0
  27. package/src/services/asg/server.js +236 -0
  28. package/src/services/asg/simulator.js +410 -0
  29. package/src/services/aurora/cluster-manager.js +731 -0
  30. package/src/services/aurora/data-api.js +514 -0
  31. package/src/services/aurora/db-engine.js +270 -0
  32. package/src/services/aurora/index.js +159 -0
  33. package/src/services/aurora/rds-api.js +189 -0
  34. package/src/services/aurora/server.js +213 -0
  35. package/src/services/aurora/sql-translator.js +334 -0
  36. package/src/services/cloudformation/index.js +2 -2
  37. package/src/services/cloudformation/server.js +1 -0
  38. package/src/services/cloudfront/cache-engine.js +225 -0
  39. package/src/services/cloudfront/geo-restrictions.js +377 -0
  40. package/src/services/cloudfront/index.js +220 -0
  41. package/src/services/cloudfront/models/behavior.js +158 -0
  42. package/src/services/cloudfront/models/distribution.js +284 -0
  43. package/src/services/cloudfront/models/index.js +99 -0
  44. package/src/services/cloudfront/models/invalidation.js +109 -0
  45. package/src/services/cloudfront/models/origin.js +155 -0
  46. package/src/services/cloudfront/origin-router.js +552 -0
  47. package/src/services/cloudfront/origin-validator.js +398 -0
  48. package/src/services/cloudfront/server.js +512 -0
  49. package/src/services/cloudfront/signed-url-generator.js +377 -0
  50. package/src/services/cloudfront/simulator.js +425 -0
  51. package/src/services/cloudfront/store.js +326 -0
  52. package/src/services/cloudshell/env-builder.js +27 -0
  53. package/src/services/cloudshell/index.js +48 -0
  54. package/src/services/cloudshell/routes.js +60 -0
  55. package/src/services/cloudshell/session-manager.js +226 -0
  56. package/src/services/cloudshell/static/.gitkeep +0 -0
  57. package/src/services/cloudshell/static/index.html +406 -0
  58. package/src/services/cloudshell/websocket-handler.js +107 -0
  59. package/src/services/cloudtrail/index.js +2 -3
  60. package/src/services/cloudtrail/server.js +68 -0
  61. package/src/services/cognito/server.js +4 -1
  62. package/src/services/dynamodb/server.js +4 -1
  63. package/src/services/dynamodb/simulator.js +2 -1
  64. package/src/services/ec2/index.js +101 -0
  65. package/src/services/ec2/instanceServer.js +311 -0
  66. package/src/services/ec2/models/ami.js +39 -0
  67. package/src/services/ec2/models/instance.js +98 -0
  68. package/src/services/ec2/models/networkInterface.js +74 -0
  69. package/src/services/ec2/models/securityGroup.js +71 -0
  70. package/src/services/ec2/server.js +350 -0
  71. package/src/services/ec2/simulator.js +436 -0
  72. package/src/services/eventbridge/server.js +36 -0
  73. package/src/services/eventbridge/simulator.js +230 -45
  74. package/src/services/iam/index.js +65 -0
  75. package/src/services/iam/middleware.js +99 -0
  76. package/src/services/iam/server.js +331 -0
  77. package/src/services/iam/simulator.js +1432 -0
  78. package/src/services/index.js +28 -0
  79. package/src/services/kinesis-analytics/index.js +105 -0
  80. package/src/services/kinesis-analytics/models/application.js +325 -0
  81. package/src/services/kinesis-analytics/models/index.js +12 -0
  82. package/src/services/kinesis-analytics/server.js +250 -0
  83. package/src/services/kinesis-analytics/simulator.js +407 -0
  84. package/src/services/kinesis-analytics/store.js +176 -0
  85. package/src/services/kinesis-data-streams/index.js +106 -0
  86. package/src/services/kinesis-data-streams/models/index.js +12 -0
  87. package/src/services/kinesis-data-streams/models/record.js +70 -0
  88. package/src/services/kinesis-data-streams/models/stream.js +280 -0
  89. package/src/services/kinesis-data-streams/server.js +255 -0
  90. package/src/services/kinesis-data-streams/simulator.js +514 -0
  91. package/src/services/kinesis-data-streams/store.js +163 -0
  92. package/src/services/kinesis-firehose/index.js +106 -0
  93. package/src/services/kinesis-firehose/models/delivery-stream.js +265 -0
  94. package/src/services/kinesis-firehose/models/index.js +10 -0
  95. package/src/services/kinesis-firehose/server.js +215 -0
  96. package/src/services/kinesis-firehose/simulator.js +388 -0
  97. package/src/services/kinesis-firehose/store.js +163 -0
  98. package/src/services/kms/server.js +4 -1
  99. package/src/services/lambda/index.js +26 -0
  100. package/src/services/lambda/server.js +12 -1
  101. package/src/services/lambda/simulator.js +4 -0
  102. package/src/services/opensearch/index.js +105 -0
  103. package/src/services/opensearch/models/document.js +115 -0
  104. package/src/services/opensearch/models/index.js +73 -0
  105. package/src/services/opensearch/server.js +310 -0
  106. package/src/services/opensearch/simulator.js +605 -0
  107. package/src/services/opensearch/store.js +162 -0
  108. package/src/services/organizations/control-tower-simulator.js +321 -0
  109. package/src/services/organizations/control-tower-store.js +182 -0
  110. package/src/services/organizations/iam-integration.js +413 -0
  111. package/src/services/organizations/index.js +172 -0
  112. package/src/services/organizations/models/index.js +13 -0
  113. package/src/services/organizations/models/invitation.js +48 -0
  114. package/src/services/organizations/models/organization.js +566 -0
  115. package/src/services/organizations/models/role.js +244 -0
  116. package/src/services/organizations/models/scp.js +131 -0
  117. package/src/services/organizations/server.js +736 -0
  118. package/src/services/organizations/simulator.js +593 -0
  119. package/src/services/organizations/store.js +235 -0
  120. package/src/services/organizations/vpc-integration.js +453 -0
  121. package/src/services/route53/index.js +67 -0
  122. package/src/services/route53/models/healthCheck.js +101 -0
  123. package/src/services/route53/models/hostedZone.js +37 -0
  124. package/src/services/route53/models/resourceRecord.js +106 -0
  125. package/src/services/route53/server.js +841 -0
  126. package/src/services/route53/simulator.js +350 -0
  127. package/src/services/s3/index.js +62 -0
  128. package/src/services/s3/presigned-url-generator.js +337 -0
  129. package/src/services/s3/server.js +231 -0
  130. package/src/services/s3/simulator.js +551 -4
  131. package/src/services/sns/index.js +31 -0
  132. package/src/services/sns/server.js +9 -0
  133. package/src/services/sqs/index.js +38 -0
  134. package/src/services/sqs/server.js +57 -4
  135. package/src/services/sqs/simulator.js +423 -33
  136. package/src/services/sts/simulator.js +209 -17
  137. package/src/services/vpc/README.md +459 -0
  138. package/src/services/vpc/ec2ResponseFormatter.js +431 -0
  139. package/src/services/vpc/index.js +90 -0
  140. package/src/services/vpc/models/internetGateway.js +71 -0
  141. package/src/services/vpc/models/routeTable.js +160 -0
  142. package/src/services/vpc/models/subnet.js +48 -0
  143. package/src/services/vpc/models/vpc.js +37 -0
  144. package/src/services/vpc/models/vpcEndpoint.js +100 -0
  145. package/src/services/vpc/models/vpcGateway.js +134 -0
  146. package/src/services/vpc/server.js +836 -0
  147. package/src/services/vpc/simulator.js +613 -0
  148. package/src/utils/policy-cache.js +215 -0
  149. package/aws-config +0 -154
@@ -0,0 +1,350 @@
1
+ /**
2
+ * Route53 Simulator
3
+ * DNS and domain management with weighted routing, health checks, and failover
4
+ */
5
+
6
+ const path = require('path');
7
+ const HostedZone = require('./models/hostedZone');
8
+ const ResourceRecord = require('./models/resourceRecord');
9
+ const HealthCheck = require('./models/healthCheck');
10
+ const LocalStore = require('../../utils/local-store');
11
+ const logger = require('../../utils/logger');
12
+
13
+ class Route53Simulator {
14
+ constructor(config = {}) {
15
+ this.config = config;
16
+ this.hostedZones = new Map();
17
+ this.resourceRecords = new Map(); // zoneId -> records[]
18
+ this.healthChecks = new Map();
19
+ this.dnsResolution = new Map(); // domain -> resolved records
20
+
21
+ // Inicializar persistência
22
+ const dataDir = process.env.AWS_LOCAL_SIMULATOR_DATA_DIR || config.dataDir || './.aws-local-simulator-data';
23
+ this.dataDir = path.join(dataDir, 'route53');
24
+ this.store = new LocalStore(this.dataDir);
25
+
26
+ // Health check interval
27
+ this.healthCheckInterval = null;
28
+ }
29
+
30
+ // Aceita tanto "abc123" quanto "/hostedzone/abc123"
31
+ normalizeZoneId(zoneId) {
32
+ if (!zoneId) return zoneId;
33
+ return zoneId.startsWith('/hostedzone/') ? zoneId : `/hostedzone/${zoneId}`;
34
+ }
35
+
36
+ async initialize() {
37
+ // 1. Carregar dados persistidos
38
+ await this.loadFromPersistence();
39
+
40
+ // 2. Criar zonas padrão se configurado
41
+ if (this.hostedZones.size === 0 && this.config.hostedZones && Array.isArray(this.config.hostedZones)) {
42
+ logger.info('📚 Carregando Zonas Hospedadas Route53 da configuração inicial...');
43
+ for (const zoneConfig of this.config.hostedZones) {
44
+ this.createHostedZone(zoneConfig.Name, zoneConfig);
45
+ }
46
+ }
47
+
48
+ // 3. Iniciar health checks periódicos
49
+ this.startHealthCheckInterval();
50
+ }
51
+
52
+ /**
53
+ * Carrega dados persistidos do storage
54
+ */
55
+ async loadFromPersistence() {
56
+ try {
57
+ // Carregar Hosted Zones
58
+ const zonesData = this.store.read('hosted-zones');
59
+ if (Array.isArray(zonesData)) {
60
+ logger.info(`📂 Carregando ${zonesData.length} Hosted Zones do storage...`);
61
+ for (const zoneData of zonesData) {
62
+ const zone = new HostedZone(zoneData.Name, zoneData.Config);
63
+ zone.Id = zoneData.Id;
64
+ zone.CallerReference = zoneData.CallerReference;
65
+ zone.CreatedDate = zoneData.CreatedDate;
66
+ zone.ResourceRecordSetCount = zoneData.ResourceRecordSetCount;
67
+ this.hostedZones.set(zone.Id, zone);
68
+ }
69
+ }
70
+
71
+ // Carregar Resource Records
72
+ const recordsData = this.store.read('resource-records');
73
+ if (Array.isArray(recordsData)) {
74
+ logger.info(`📂 Carregando ${recordsData.length} Resource Records do storage...`);
75
+ for (const recordGroup of recordsData) {
76
+ const records = [];
77
+ for (const recordData of recordGroup.records) {
78
+ const record = new ResourceRecord(recordData.Name, recordData.Type, recordData.ResourceRecords.map(r => r.Value), recordData);
79
+ record.CreatedDate = recordData.CreatedDate;
80
+ record.LastModified = recordData.LastModified;
81
+ records.push(record);
82
+ }
83
+ this.resourceRecords.set(recordGroup.zoneId, records);
84
+ }
85
+ }
86
+
87
+ // Carregar Health Checks
88
+ const healthChecksData = this.store.read('health-checks');
89
+ if (Array.isArray(healthChecksData)) {
90
+ logger.info(`📂 Carregando ${healthChecksData.length} Health Checks do storage...`);
91
+ for (const hcData of healthChecksData) {
92
+ const hc = new HealthCheck(hcData.HealthCheckConfig);
93
+ hc.Id = hcData.Id;
94
+ hc.Status = hcData.Status;
95
+ hc.CreatedDate = hcData.CreatedDate;
96
+ this.healthChecks.set(hc.Id, hc);
97
+ }
98
+ }
99
+ } catch (error) {
100
+ logger.error(`Erro ao carregar persistência Route53: ${error.message}`);
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Persiste dados para storage
106
+ */
107
+ persistData() {
108
+ try {
109
+ // Persistir Hosted Zones
110
+ const zonesData = Array.from(this.hostedZones.values()).map(zone => zone.toJSON());
111
+ this.store.write('hosted-zones', zonesData);
112
+
113
+ // Persistir Resource Records
114
+ const recordsData = Array.from(this.resourceRecords.entries()).map(([zoneId, records]) => ({
115
+ zoneId,
116
+ records: records.map(r => r.toJSON()),
117
+ }));
118
+ this.store.write('resource-records', recordsData);
119
+
120
+ // Persistir Health Checks
121
+ const healthChecksData = Array.from(this.healthChecks.values()).map(hc => hc.toJSON());
122
+ this.store.write('health-checks', healthChecksData);
123
+ } catch (error) {
124
+ logger.error(`Erro ao persistir Route53: ${error.message}`);
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Cria uma zona hospedada
130
+ */
131
+ createHostedZone(name, config = {}) {
132
+ const zone = new HostedZone(name, config);
133
+ this.hostedZones.set(zone.Id, zone);
134
+ this.resourceRecords.set(zone.Id, []);
135
+ this.persistData();
136
+ logger.info(`✅ Hosted Zone criada: ${name} (${zone.Id})`);
137
+ return zone;
138
+ }
139
+
140
+ /**
141
+ * Lista zonas hospedadas
142
+ */
143
+ listHostedZones() {
144
+ return Array.from(this.hostedZones.values());
145
+ }
146
+
147
+ /**
148
+ * Obtém zona hospedada
149
+ */
150
+ getHostedZone(zoneId) {
151
+ return this.hostedZones.get(this.normalizeZoneId(zoneId));
152
+ }
153
+
154
+ /**
155
+ * Deleta zona hospedada
156
+ */
157
+ deleteHostedZone(zoneId) {
158
+ zoneId = this.normalizeZoneId(zoneId);
159
+ const zone = this.hostedZones.get(zoneId);
160
+ if (!zone) throw new Error(`Hosted Zone não encontrada: ${zoneId}`);
161
+
162
+ // Verificar se tem registros
163
+ const records = this.resourceRecords.get(zoneId) || [];
164
+ if (records.length > 2) { // NS e SOA são criados automaticamente
165
+ throw new Error(`Não pode deletar zona com registros: ${records.length} encontrados`);
166
+ }
167
+
168
+ this.hostedZones.delete(zoneId);
169
+ this.resourceRecords.delete(zoneId);
170
+ this.persistData();
171
+ logger.info(`✅ Hosted Zone deletada: ${zoneId}`);
172
+ return true;
173
+ }
174
+
175
+ /**
176
+ * Cria um resource record
177
+ */
178
+ createResourceRecord(zoneId, name, type, values, config = {}) {
179
+ zoneId = this.normalizeZoneId(zoneId);
180
+ if (!this.hostedZones.has(zoneId)) {
181
+ throw new Error(`Hosted Zone não encontrada: ${zoneId}`);
182
+ }
183
+
184
+ if (!this.resourceRecords.has(zoneId)) {
185
+ this.resourceRecords.set(zoneId, []);
186
+ }
187
+
188
+ const record = new ResourceRecord(name, type, values, config);
189
+ const records = this.resourceRecords.get(zoneId);
190
+ records.push(record);
191
+
192
+ const zone = this.hostedZones.get(zoneId);
193
+ zone.ResourceRecordSetCount = records.length;
194
+
195
+ this.persistData();
196
+ logger.info(`✅ Resource Record criado: ${name} (${type})`);
197
+ return record;
198
+ }
199
+
200
+ /**
201
+ * Lista resource records
202
+ */
203
+ listResourceRecords(zoneId) {
204
+ zoneId = this.normalizeZoneId(zoneId);
205
+ if (!this.hostedZones.has(zoneId)) {
206
+ throw new Error(`Hosted Zone não encontrada: ${zoneId}`);
207
+ }
208
+ return this.resourceRecords.get(zoneId) || [];
209
+ }
210
+
211
+ /**
212
+ * Deleta resource record
213
+ */
214
+ deleteResourceRecord(zoneId, name, type) {
215
+ zoneId = this.normalizeZoneId(zoneId);
216
+ if (!this.resourceRecords.has(zoneId)) {
217
+ throw new Error(`Hosted Zone não encontrada: ${zoneId}`);
218
+ }
219
+
220
+ const records = this.resourceRecords.get(zoneId);
221
+ const index = records.findIndex(r => r.Name === name && r.Type === type);
222
+
223
+ if (index === -1) {
224
+ throw new Error(`Resource Record não encontrado: ${name} (${type})`);
225
+ }
226
+
227
+ records.splice(index, 1);
228
+
229
+ const zone = this.hostedZones.get(zoneId);
230
+ zone.ResourceRecordSetCount = records.length;
231
+
232
+ this.persistData();
233
+ logger.info(`✅ Resource Record deletado: ${name} (${type})`);
234
+ return true;
235
+ }
236
+
237
+ /**
238
+ * Cria um health check
239
+ */
240
+ createHealthCheck(config = {}) {
241
+ const healthCheck = new HealthCheck(config);
242
+ this.healthChecks.set(healthCheck.Id, healthCheck);
243
+ this.persistData();
244
+ logger.info(`✅ Health Check criado: ${healthCheck.Id}`);
245
+ return healthCheck;
246
+ }
247
+
248
+ /**
249
+ * Lista health checks
250
+ */
251
+ listHealthChecks() {
252
+ return Array.from(this.healthChecks.values());
253
+ }
254
+
255
+ /**
256
+ * Obtém health check
257
+ */
258
+ getHealthCheck(id) {
259
+ return this.healthChecks.get(id);
260
+ }
261
+
262
+ /**
263
+ * Deleta health check
264
+ */
265
+ deleteHealthCheck(id) {
266
+ if (!this.healthChecks.has(id)) {
267
+ throw new Error(`Health Check não encontrado: ${id}`);
268
+ }
269
+
270
+ this.healthChecks.delete(id);
271
+ this.persistData();
272
+ logger.info(`✅ Health Check deletado: ${id}`);
273
+ return true;
274
+ }
275
+
276
+ /**
277
+ * Resolve DNS query (simular resolução)
278
+ */
279
+ resolveDNS(domain, type = 'A', queryType = 'weighted') {
280
+ // Procurar a zona apropriada
281
+ let zone = null;
282
+ let zoneDomain = null;
283
+
284
+ for (const z of this.hostedZones.values()) {
285
+ if (domain.endsWith(z.Name.replace(/\.$/, ''))) {
286
+ zone = z;
287
+ zoneDomain = z.Name;
288
+ break;
289
+ }
290
+ }
291
+
292
+ if (!zone) {
293
+ throw new Error(`Zona não encontrada para domínio: ${domain}`);
294
+ }
295
+
296
+ const records = this.resourceRecords.get(zone.Id) || [];
297
+ let matchedRecords = records.filter(r => r.Name === domain && r.Type === type);
298
+
299
+ if (matchedRecords.length === 0) {
300
+ throw new Error(`Nenhum registro encontrado para: ${domain} (${type})`);
301
+ }
302
+
303
+ // Aplicar estratégia de resolução
304
+ let selected = matchedRecords;
305
+ if (queryType === 'weighted' && matchedRecords.some(r => r.Weight !== null)) {
306
+ selected = ResourceRecord.selectWeightedRecords(matchedRecords);
307
+ } else if (queryType === 'geolocation') {
308
+ selected = ResourceRecord.selectGeoLocationRecords(matchedRecords);
309
+ } else if (queryType === 'failover' && matchedRecords.some(r => r.Failover)) {
310
+ const healthyIds = Array.from(this.healthChecks.values())
311
+ .filter(hc => hc.Status === 'Healthy')
312
+ .map(hc => hc.Id);
313
+ selected = ResourceRecord.selectFailoverRecords(matchedRecords, healthyIds);
314
+ }
315
+
316
+ return {
317
+ domain,
318
+ type,
319
+ records: selected,
320
+ resolvedAt: new Date().toISOString(),
321
+ };
322
+ }
323
+
324
+ /**
325
+ * Inicia health check periódico
326
+ */
327
+ startHealthCheckInterval() {
328
+ if (this.healthCheckInterval) clearInterval(this.healthCheckInterval);
329
+
330
+ this.healthCheckInterval = setInterval(() => {
331
+ for (const hc of this.healthChecks.values()) {
332
+ hc.check();
333
+ }
334
+ this.persistData();
335
+ }, 30000); // 30 segundos
336
+ }
337
+
338
+ /**
339
+ * Para health check periódico
340
+ */
341
+ stopHealthCheckInterval() {
342
+ if (this.healthCheckInterval) {
343
+ clearInterval(this.healthCheckInterval);
344
+ this.healthCheckInterval = null;
345
+ }
346
+ }
347
+ }
348
+
349
+ module.exports = Route53Simulator;
350
+
@@ -34,6 +34,68 @@ class S3Service {
34
34
  injectDependencies(server) {
35
35
  const ct = server.getService('cloudtrail');
36
36
  if (ct?.simulator) this.simulator.audit.setTrail(ct.simulator);
37
+
38
+ const lambda = server.getService('lambda');
39
+ if (lambda) this.simulator.setLambdaService(lambda);
40
+
41
+ const sns = server.getService('sns');
42
+ if (sns) this.simulator.setSnsService(sns);
43
+
44
+ const sqs = server.getService('sqs');
45
+ if (sqs) this.simulator.setSqsService(sqs);
46
+
47
+ const eventbridge = server.getService('eventbridge');
48
+ if (eventbridge) this.simulator.setEventbridgeService(eventbridge);
49
+
50
+ // IAM enforcement middleware (only when IAM service is active)
51
+ const iamService = server.getService('iam');
52
+ if (iamService && iamService.getSimulator()) {
53
+ const { createIAMEnforcementMiddleware } = require('../iam/middleware');
54
+ const iamSimulator = iamService.getSimulator();
55
+
56
+ const middleware = createIAMEnforcementMiddleware({
57
+ iamSimulator,
58
+ servicePrefix: 's3',
59
+ extractAction: (req) => {
60
+ const pathParts = req.path.split('/').filter(Boolean);
61
+ const hasKey = pathParts.length >= 2;
62
+
63
+ switch (req.method) {
64
+ case 'GET':
65
+ return hasKey ? 'GetObject' : 'ListBucket';
66
+ case 'PUT':
67
+ return hasKey ? 'PutObject' : 'CreateBucket';
68
+ case 'DELETE':
69
+ return hasKey ? 'DeleteObject' : 'DeleteBucket';
70
+ case 'HEAD':
71
+ return hasKey ? 'GetObject' : 'ListBucket';
72
+ case 'POST':
73
+ return 'PutObject';
74
+ default:
75
+ return 'Unknown';
76
+ }
77
+ },
78
+ extractResource: (req) => {
79
+ const pathParts = req.path.split('/').filter(Boolean);
80
+ const bucket = pathParts[0] || 'unknown';
81
+ const key = pathParts.slice(1).join('/');
82
+ if (key) return `arn:aws:s3:::${bucket}/${key}`;
83
+ return `arn:aws:s3:::${bucket}`;
84
+ },
85
+ extractConditionContext: (req) => ({
86
+ 's3:prefix': req.query?.prefix || '',
87
+ 's3:delimiter': req.query?.delimiter || ''
88
+ }),
89
+ formatError: (res, code, message) => {
90
+ res.status(403).set('Content-Type', 'application/xml').send(
91
+ `<?xml version="1.0" encoding="UTF-8"?><Error><Code>${code}</Code><Message>${message}</Message></Error>`
92
+ );
93
+ }
94
+ });
95
+
96
+ // Set middleware on the S3 server so it's applied before routes
97
+ this.server.iamMiddleware = middleware;
98
+ }
37
99
  }
38
100
 
39
101
  async start() {