@@ -50,19 +50,19 @@ export class ApiStack extends cdk.Stack {
5050 const vapidSecret = secretsmanager . Secret . fromSecretNameV2 ( this , 'VapidSecret' , vapidSecretName )
5151
5252 // 3) SNS Topic for error notifications(オプション: メール通知が必要な場合のみ)
53- const errorTopic = props . alertEmail ? new sns . Topic ( this , 'ErrorNotificationTopic' , {
54- displayName : 'Handwash API Error Notifications' ,
55- } ) : undefined
56-
53+ const errorTopic = props . alertEmail
54+ ? new sns . Topic ( this , 'ErrorNotificationTopic' , {
55+ displayName : 'Handwash API Error Notifications' ,
56+ } )
57+ : undefined
58+
5759 // メールアドレスが指定されている場合はサブスクリプションを追加
5860 if ( props . alertEmail && errorTopic ) {
59- errorTopic . addSubscription (
60- new subscriptions . EmailSubscription ( props . alertEmail )
61- )
61+ errorTopic . addSubscription ( new subscriptions . EmailSubscription ( props . alertEmail ) )
6262 }
6363
6464 // 3) Lambda functions(コスト最適化: 最小限の設定)
65- const lambdaEnv = {
65+ const lambdaEnv = {
6666 TABLE_NAME : table . tableName ,
6767 ...( errorTopic && { ERROR_TOPIC_ARN : errorTopic . topicArn } ) ,
6868 }
@@ -206,12 +206,21 @@ export class ApiStack extends cdk.Stack {
206206 // SNS Topicへのパブリッシュ権限(エラー通知がある場合のみ)
207207 if ( errorTopic ) {
208208 const lambdaFunctions = [
209- meFn , createFamilyFn , listFamiliesFn , joinFamilyFn ,
210- createHandwashEventFn , listHandwashEventsFn , pushSubscribeFn ,
211- sendReminderFn , listFamilyMembersFn , sendPushToUserFn ,
212- leaveFamilyFn , deleteFamilyFn , updateProfileFn ,
209+ meFn ,
210+ createFamilyFn ,
211+ listFamiliesFn ,
212+ joinFamilyFn ,
213+ createHandwashEventFn ,
214+ listHandwashEventsFn ,
215+ pushSubscribeFn ,
216+ sendReminderFn ,
217+ listFamilyMembersFn ,
218+ sendPushToUserFn ,
219+ leaveFamilyFn ,
220+ deleteFamilyFn ,
221+ updateProfileFn ,
213222 ]
214- lambdaFunctions . forEach ( fn => errorTopic . grantPublish ( fn ) )
223+ lambdaFunctions . forEach ( ( fn ) => errorTopic . grantPublish ( fn ) )
215224 }
216225
217226 // 5) EventBridge Scheduler for daily reminder (20:00 JST = 11:00 UTC)
@@ -223,114 +232,30 @@ export class ApiStack extends cdk.Stack {
223232 targets : [ new targets . LambdaFunction ( sendReminderFn ) ] ,
224233 } )
225234
226- // 6) WAF v2 WebACL
227- // 注意: HTTP API (apigatewayv2) にはWAFを直接アタッチできません
228- // WAFを使う場合は REST API (apigateway) に切り替える必要があります
229- // または CloudFront + WAF (CLOUDFRONT scope) を前段に置く方法もあります
230- // 一旦、WAF関連のコードはコメントアウトしてデプロイを通します
231- //
232- // const webAcl = new wafv2.CfnWebACL(this, 'ApiWebACL', {
233- // defaultAction: { allow: {} },
234- // scope: 'REGIONAL', // API Gateway用
235- // visibilityConfig: {
236- // sampledRequestsEnabled: true,
237- // cloudWatchMetricsEnabled: true,
238- // metricName: 'ApiWebACL',
239- // },
240- // rules: [
241- // // AWS Managed Rules - Core Rule Set
242- // {
243- // name: 'AWSManagedRulesCommonRuleSet',
244- // priority: 1,
245- // overrideAction: { none: {} },
246- // statement: {
247- // managedRuleGroupStatement: {
248- // vendorName: 'AWS',
249- // name: 'AWSManagedRulesCommonRuleSet',
250- // },
251- // },
252- // visibilityConfig: {
253- // sampledRequestsEnabled: true,
254- // cloudWatchMetricsEnabled: true,
255- // metricName: 'CommonRuleSet',
256- // },
257- // },
258- // // AWS Managed Rules - Known Bad Inputs
259- // {
260- // name: 'AWSManagedRulesKnownBadInputsRuleSet',
261- // priority: 2,
262- // overrideAction: { none: {} },
263- // statement: {
264- // managedRuleGroupStatement: {
265- // vendorName: 'AWS',
266- // name: 'AWSManagedRulesKnownBadInputsRuleSet',
267- // },
268- // },
269- // visibilityConfig: {
270- // sampledRequestsEnabled: true,
271- // cloudWatchMetricsEnabled: true,
272- // metricName: 'KnownBadInputs',
273- // },
274- // },
275- // // レート制限ルール(1分間に100リクエスト/IP)
276- // {
277- // name: 'RateLimitRule',
278- // priority: 0, // 最優先
279- // action: {
280- // block: {},
281- // },
282- // statement: {
283- // rateBasedStatement: {
284- // limit: 100, // 1分間あたり100リクエスト
285- // aggregateKeyType: 'IP',
286- // },
287- // },
288- // visibilityConfig: {
289- // sampledRequestsEnabled: true,
290- // cloudWatchMetricsEnabled: true,
291- // metricName: 'RateLimitRule',
292- // },
293- // },
294- // ],
295- // })
296-
297235 // 6) HTTP API
298236 const httpApi = new apigwv2 . HttpApi ( this , 'HttpApi' , {
299237 corsPreflight : {
300- allowOrigins : [
301- 'http://localhost:5173' ,
302- `https://${ props . webDistributionDomain } ` ,
238+ allowOrigins : [ 'http://localhost:5173' , `https://${ props . webDistributionDomain } ` ] ,
239+ allowMethods : [
240+ apigwv2 . CorsHttpMethod . GET ,
241+ apigwv2 . CorsHttpMethod . POST ,
242+ apigwv2 . CorsHttpMethod . PUT ,
243+ apigwv2 . CorsHttpMethod . OPTIONS ,
303244 ] ,
304- allowMethods : [ apigwv2 . CorsHttpMethod . GET , apigwv2 . CorsHttpMethod . POST , apigwv2 . CorsHttpMethod . PUT , apigwv2 . CorsHttpMethod . OPTIONS ] ,
305- allowHeaders : [ 'authorization' , 'content-type' ] ,
306- } ,
307- } )
308-
309- // HTTP API Stage(極端に低いスロットリング設定)
310- // 既存の$defaultステージを更新するため、CfnStageを使用
311- const httpStage = new apigwv2 . CfnStage ( this , 'HttpStage' , {
312- apiId : httpApi . apiId ,
313- stageName : '$default' ,
314- // 極端に低いスロットリング: 1秒あたり2リクエスト、バースト5
315- defaultRouteSettings : {
316- throttlingRateLimit : 2 , // 1秒あたり2リクエスト(過剰リクエストを完全にブロック)
317- throttlingBurstLimit : 5 , // バースト時5リクエスト
245+ // 大文字小文字揺れに備えて両方許可(ブラウザ/ライブラリ差異対策)
246+ allowHeaders : [ 'authorization' , 'Authorization' , 'content-type' , 'Content-Type' ] ,
318247 } ,
319248 } )
320249
321- // WAFをAPI Gatewayにアタッチ
322- // 注意: HTTP API (apigatewayv2) にはWAFを直接アタッチできません
323- // WAFを使う場合は REST API (apigateway) に切り替える必要があります
324- // または CloudFront + WAF (CLOUDFRONT scope) を前段に置く方法もあります
325- // 一旦、WAF Associationはコメントアウトしてデプロイを通します
326- //
327- // const httpApiId = httpApi.apiId
328- // const httpApiStageArn = `arn:aws:apigateway:${this.region}::/apis/${httpApiId}/stages/$default`
329- //
330- // new wafv2.CfnWebACLAssociation(this, 'ApiWebACLAssociation', {
331- // resourceArn: httpApiStageArn,
332- // webAclArn: webAcl.attrArn,
333- // })
250+ // ★重要:$default stage を「新規作成」しない(Stage already exists 対策)
251+ // 既存の defaultStage を更新してスロットリングを設定する
252+ const cfnDefaultStage = httpApi . defaultStage ?. node . defaultChild as apigwv2 . CfnStage
253+ if ( cfnDefaultStage ) {
254+ cfnDefaultStage . defaultRouteSettings = {
255+ throttlingRateLimit : 2 , // 1秒あたり2リクエスト
256+ throttlingBurstLimit : 5 , // バースト時5リクエスト
257+ }
258+ }
334259
335260 // 7) JWT Authorizer
336261 const jwtAuthorizer = new authorizers . HttpJwtAuthorizer (
@@ -425,7 +350,6 @@ export class ApiStack extends cdk.Stack {
425350 } )
426351
427352 // 9) CloudWatch Alarms(最小限に削減: エラー通知がある場合のみ)
428- // 利用されない予定なので、アラームは最小限に(または完全に無効化)
429353 if ( errorTopic ) {
430354 // 重大なエラーのみ監視(API Gatewayの5xxエラーのみ)
431355 const apiServerErrorAlarm = new cloudwatch . Alarm ( this , 'ApiServerErrorRateAlarm' , {
@@ -446,7 +370,7 @@ export class ApiStack extends cdk.Stack {
446370 new cdk . CfnOutput ( this , 'ApiUrl' , { value : httpApi . apiEndpoint } )
447371 new cdk . CfnOutput ( this , 'TableName' , { value : table . tableName } )
448372 if ( errorTopic ) {
449- new cdk . CfnOutput ( this , 'ErrorTopicArn' , {
373+ new cdk . CfnOutput ( this , 'ErrorTopicArn' , {
450374 value : errorTopic . topicArn ,
451375 description : 'SNS Topic ARN for error notifications' ,
452376 } )
0 commit comments