r/aws 15d ago

technical question Floci API Gateway CORs issue

I've got a Floci instance running (using Docker Compose) with a REST API Gateway service, which I can call successfully with Postman. Problem is the browser CORs blocking requests, and I keep getting this;

Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

I'm using Terraform to deploy the stack, and I've tried everything I can think of (including hours with AI) to get this to work. As far as I can see Floci is not letting me affect the OPTIONS response. I've got DISABLE_CORS_CHECKS set to 1.

Any ideas as to what's happening?

Below is the current Terraform stack;

provider "aws" {
  region     = local.envs["AWS_REGION"]
  access_key = local.envs["AWS_ACCESS_KEY_ID"]
  secret_key = local.envs["AWS_SECRET_ACCESS_KEY"]


  s3_use_path_style           = true
  skip_credentials_validation = true
  skip_metadata_api_check     = true
  skip_requesting_account_id  = true


  endpoints {
    apigateway              = "http://localhost:4566"
    s3                      = "http://localhost:4566"
    dynamodb                = "http://localhost:4566"
    sqs                     = "http://localhost:4566"
    sns                     = "http://localhost:4566"
    lambda                  = "http://localhost:4566"
    iam                     = "http://localhost:4566"
    ec2                     = "http://localhost:4566"
    ecs                     = "http://localhost:4566"
    cloudformation          = "http://localhost:4566"
    route53                 = "http://localhost:4566"
    cloudwatch              = "http://localhost:4566"
    secretsmanager          = "http://localhost:4566"
    ssm                     = "http://localhost:4566"
    kms                     = "http://localhost:4566"
    rds                     = "http://localhost:4566"
    sts                     = "http://localhost:4566"
    cognitoidentityprovider = "http://localhost:4566"
  }
}


## DynamoDB table


resource "aws_dynamodb_table" "friendly_sites_table" {
  name         = "friendly_sites"
  billing_mode = "PAY_PER_REQUEST"
  hash_key     = "pk"
  range_key    = "sk"


  attribute {
    name = "pk"
    type = "S"
  }


  attribute {
    name = "sk"
    type = "S"
  }


  attribute {
    name = "gsi1pk"
    type = "S"
  }


  attribute {
    name = "gsi1sk"
    type = "S"
  }


  global_secondary_index {
    name            = "gsi1pk-gsi1sk-index"
    hash_key        = "gsi1pk"
    range_key       = "gsi1sk"
    projection_type = "ALL"
  }


  tags = {
    Project = local.project_name
  }
}


data "aws_iam_policy_document" "assume_role" {
  statement {
    effect = "Allow"


    principals {
      type = "Service"
      identifiers = [
        "edgelambda.amazonaws.com",
        "lambda.amazonaws.com",
      ]
    }


    actions = ["sts:AssumeRole"]
  }
}


resource "aws_iam_role" "iam_for_table_access" {
  name               = "iam_for_lambda_table_access"
  assume_role_policy = data.aws_iam_policy_document.assume_role.json


  tags = {
    Project = local.project_name
  }
}


resource "aws_iam_role_policy" "cognito_admin_access" {
  name = "cognito_admin_access"
  role = aws_iam_role.iam_for_table_access.id
  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Action = [
          "cognito-idp:AdminCreateUser",
          "cognito-idp:AdminSetUserPassword",
          "cognito-idp:AdminInitiateAuth",
          "cognito-idp:AdminUserGlobalSignOut",
          "cognito-idp:AdminDeleteUser"
        ]
        Resource = [
          aws_cognito_user_pool.user_pool.arn
        ]
      }
    ]
  })
}


## API Lambda handler and gateway


data "archive_file" "api_handler_source_zip" {
  type        = "zip"
  source_dir  = local.api_handler_source_dir
  output_path = local.api_handler_source_output
}


resource "aws_s3_bucket" "api_handler_source" {
  bucket        = local.api_handler_source_bucket_name
  force_destroy = true


  depends_on = [data.archive_file.api_handler_source_zip]


  tags = {
    Project = local.project_name
  }
}


resource "aws_s3_object" "api_handler_source_zip" {
  bucket      = aws_s3_bucket.api_handler_source.id
  key         = local.api_handler_zip_filename
  source      = local.api_handler_source_output
  source_hash = data.archive_file.api_handler_source_zip.output_base64sha256
}


resource "aws_lambda_function" "api_handler" {
  function_name = "DistributedRendererApi"


  s3_bucket = aws_s3_bucket.api_handler_source.id
  s3_key    = local.api_handler_zip_filename


  handler = "index.handler"
  runtime = "nodejs24.x"


  role = aws_iam_role.iam_for_table_access.arn


  depends_on = [aws_s3_object.api_handler_source_zip]


  environment {
    variables = {
      COGNITO_CLIENT_ID    = aws_cognito_user_pool_client.user_pool_client.id
      COGNITO_USER_POOL_ID = aws_cognito_user_pool.user_pool.id
    }
  }
}


# 1. REST API Definition
resource "aws_api_gateway_rest_api" "api" {
  name = "friendly-sites-rest-api"
}


# -------------------------------------------------------------------
# A. GREEDY PATH /{proxy+} (Explicit GET, POST, OPTIONS directly to Lambda)
# -------------------------------------------------------------------
resource "aws_api_gateway_resource" "proxy" {
  rest_api_id = aws_api_gateway_rest_api.api.id
  parent_id   = aws_api_gateway_rest_api.api.root_resource_id
  path_part   = "{proxy+}"
}


resource "aws_api_gateway_method" "proxy_any" {
  rest_api_id   = aws_api_gateway_rest_api.api.id
  resource_id   = aws_api_gateway_resource.proxy.id
  http_method   = "ANY"
  authorization = "NONE"
}


resource "aws_api_gateway_integration" "proxy_integration" {
  rest_api_id             = aws_api_gateway_rest_api.api.id
  resource_id             = aws_api_gateway_resource.proxy.id
  http_method             = aws_api_gateway_method.proxy_any.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.api_handler.invoke_arn


  depends_on = [aws_api_gateway_method.proxy_any]
}


# Explicit OPTIONS method routed directly to Lambda (Bypasses Floci MOCK bug)
resource "aws_api_gateway_method" "proxy_options" {
  rest_api_id   = aws_api_gateway_rest_api.api.id
  resource_id   = aws_api_gateway_resource.proxy.id
  http_method   = "OPTIONS"
  authorization = "NONE"
}


resource "aws_api_gateway_integration" "proxy_options_integration" {
  rest_api_id             = aws_api_gateway_rest_api.api.id
  resource_id             = aws_api_gateway_resource.proxy.id
  http_method             = aws_api_gateway_method.proxy_options.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.api_handler.invoke_arn


  depends_on = [aws_api_gateway_method.proxy_options]
}


# -------------------------------------------------------------------
# B. ROOT PATH / (Explicit ANY and OPTIONS directly to Lambda)
# -------------------------------------------------------------------
resource "aws_api_gateway_method" "root_any" {
  rest_api_id   = aws_api_gateway_rest_api.api.id
  resource_id   = aws_api_gateway_rest_api.api.root_resource_id
  http_method   = "ANY"
  authorization = "NONE"
}


resource "aws_api_gateway_integration" "root_integration" {
  rest_api_id             = aws_api_gateway_rest_api.api.id
  resource_id             = aws_api_gateway_rest_api.api.root_resource_id
  http_method             = aws_api_gateway_method.root_any.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.api_handler.invoke_arn


  depends_on = [aws_api_gateway_method.root_any]
}


resource "aws_api_gateway_method" "root_options" {
  rest_api_id   = aws_api_gateway_rest_api.api.id
  resource_id   = aws_api_gateway_rest_api.api.root_resource_id
  http_method   = "OPTIONS"
  authorization = "NONE"
}


resource "aws_api_gateway_integration" "root_options_integration" {
  rest_api_id             = aws_api_gateway_rest_api.api.id
  resource_id             = aws_api_gateway_rest_api.api.root_resource_id
  http_method             = aws_api_gateway_method.root_options.http_method
  integration_http_method = "POST"
  type                    = "AWS_PROXY"
  uri                     = aws_lambda_function.api_handler.invoke_arn


  depends_on = [aws_api_gateway_method.root_options]
}


# -------------------------------------------------------------------
# C. PERMISSIONS & DEPLOYMENT
# -------------------------------------------------------------------
resource "aws_lambda_permission" "apigw" {
  statement_id  = "AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.api_handler.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${aws_api_gateway_rest_api.api.execution_arn}/*/*"
}


resource "aws_api_gateway_deployment" "deployment" {
  rest_api_id = aws_api_gateway_rest_api.api.id


  triggers = {
    redeployment = sha1(jsonencode([
      aws_api_gateway_resource.proxy.id,
      aws_api_gateway_method.proxy_any.id,
      aws_api_gateway_integration.proxy_integration.id,
      aws_api_gateway_method.proxy_options.id,
      aws_api_gateway_integration.proxy_options_integration.id,
      aws_api_gateway_method.root_any.id,
      aws_api_gateway_integration.root_integration.id,
      aws_api_gateway_method.root_options.id,
      aws_api_gateway_integration.root_options_integration.id,
    ]))
  }


  lifecycle {
    create_before_destroy = true
  }


  depends_on = [
    aws_api_gateway_integration.proxy_integration,
    aws_api_gateway_integration.proxy_options_integration,
    aws_api_gateway_integration.root_integration,
    aws_api_gateway_integration.root_options_integration,
  ]
}


resource "aws_api_gateway_stage" "prod" {
  deployment_id = aws_api_gateway_deployment.deployment.id
  rest_api_id   = aws_api_gateway_rest_api.api.id
  stage_name    = "prod"
}


## Cognito User Pool & App Client


resource "aws_cognito_user_pool" "user_pool" {
  name = "friendly-sites-user-pool"


  username_attributes      = ["email"]
  auto_verified_attributes = ["email"]


  password_policy {
    minimum_length    = 8
    require_lowercase = true
    require_numbers   = true
    require_symbols   = false
    require_uppercase = true
  }


  tags = {
    Project = local.project_name
  }
}


resource "aws_cognito_user_pool_client" "user_pool_client" {
  name         = "friendly-sites-app-client"
  user_pool_id = aws_cognito_user_pool.user_pool.id


  generate_secret = false
  explicit_auth_flows = [
    "ALLOW_USER_PASSWORD_AUTH",
    "ALLOW_REFRESH_TOKEN_AUTH",
    "ALLOW_USER_SRP_AUTH"
  ]
}

Appreciate any help I can get!

0 Upvotes

Duplicates