https://serverless-stack.com/ logo
#help
Title
# help
a

Artemiy Davydov

03/18/2022, 8:04 AM
Now I have a new CORS error
PreflightWildcardOriginNotAllowed
I need to set the exact origin for preflight too. I can get the
WebStatic
URL if I create it before the REST API, but then how do I pass the API URL to the frontend?
f

Frank

03/18/2022, 8:12 AM
Hi @Artemiy Davydov, can u share a snippet of how u r creating the Api and the StaticSite? I will shown an example using ur snippet.
a

Artemiy Davydov

03/18/2022, 8:14 AM
Copy code
import * as sst from "@serverless-stack/resources";
import { TableFieldType } from "@serverless-stack/resources";
import path from "path";
import { CorsHttpMethod } from "@aws-cdk/aws-apigatewayv2-alpha";

export default class CoreStack extends sst.Stack {
  constructor(scope: <http://sst.App|sst.App>, id: string, props?: sst.StackProps) {
    super(scope, id, props);

    const usersTable = new sst.Table(this, "UsersTable", {
      fields: {
        email: TableFieldType.STRING,
      },
      primaryIndex: { partitionKey: "email" },
    });

    const linksTable = new sst.Table(this, "LinksTable", {
      fields: {
        id: TableFieldType.STRING,
      },
      primaryIndex: { partitionKey: "id" },
    });

    const api = new sst.Api(this, "Api", {
      cors: {
        allowHeaders: ["Origin", "X-Requested-With", "Content-Type", "Accept"],
        allowMethods: [
          CorsHttpMethod.OPTIONS,
          <http://CorsHttpMethod.POST|CorsHttpMethod.POST>,
          CorsHttpMethod.GET,
          CorsHttpMethod.PUT,
        ],
        allowOrigins: [
          "<http://localhost:3000>",
          "I NEED TO PUT FRONTEND URL HERE",
        ],
        allowCredentials: true,
      },
      defaultFunctionProps: {
        // See /esbuild.js. This is necessary to support decorators
        bundle: {
          esbuildConfig: {
            plugins: "esbuild-decorators-plugin.js",
          },
        },
        permissions: [usersTable, linksTable],
      },
      routes: {
        "GET /{id}": "src/functions/_id/get/handler.main",
        "DELETE /links/{id}": "src/functions/links/_id/delete/handler.main",
        "GET /links/{id}": "src/functions/links/_id/get/handler.main",
        "PUT /links/{id}": "src/functions/links/_id/put/handler.main",
        "POST /links/create": "src/functions/links/create/handler.main",
        "GET /links/list": "src/functions/links/list/handler.main",
        "GET /auth/redirect": "src/functions/auth/redirect/handler.main",
        "GET /users/me": "src/functions/users/me/handler.main",
      },
    });

    const webStatic = new sst.ReactStaticSite(this, "WebStatic", {
      path: path.resolve(__dirname, "../../frontend"),
      environment: {
        REACT_APP_API_URL: api.url,
      },
      cfDistribution: {
        comment: "Distribution for React website",
      },
    });

    const fns = this.getAllFunctions();
    const env = {
      GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID || "",
      GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET || "",
      USERS_TABLE_NAME: usersTable.tableName,
      LINKS_TABLE_NAME: linksTable.tableName,
      API_URL: api.url,
      WEB_URL: webStatic.url,
    };
    for (const fn of fns) {
      for (const key in env) {
        fn.addEnvironment(key, env[key as keyof typeof env]);
      }
    }

    this.addOutputs({
      ApiEndpoint: api.url,
      WebEndpoint: webStatic.url,
    });
  }
}
f

Frank

03/18/2022, 8:17 AM
Copy code
let webStatic;

    const api = new sst.Api(this, "Api", {
      cors: {
        allowHeaders: [ ... ],
        allowMethods: [ ... ],
        allowOrigins: [
          "<http://localhost:3000>",
          Lazy.stringValue({
            produce(context) {
              return webStatic.url;
            }
          }),
        ],
        allowCredentials: true,
      },
      ...
    };
a

Artemiy Davydov

03/18/2022, 8:18 AM
But how to put
REACT_APP_API_URL
to webStatic now?
f

Frank

03/18/2022, 8:18 AM
The rest can stay the same
a

Artemiy Davydov

03/18/2022, 8:37 AM
Copy code
let webStatic: sst.ReactStaticSite | null = null;

    const api = new sst.Api(this, "Api", {
      cors: {
        allowHeaders: ["Origin", "X-Requested-With", "Content-Type", "Accept"],
        allowMethods: [
          CorsHttpMethod.OPTIONS,
          <http://CorsHttpMethod.POST|CorsHttpMethod.POST>,
          CorsHttpMethod.GET,
          CorsHttpMethod.PUT,
        ],
        allowOrigins: [
          "<http://localhost:3000>",
          Lazy.string({
            produce() {
              return webStatic?.url;
            },
          }),
        ],
....

    webStatic = new sst.ReactStaticSite(this, "WebStatic", {
      path: path.resolve(__dirname, "../../frontend"),
      environment: {
        REACT_APP_API_URL: api.url,
      },
      cfDistribution: {
        comment: "Distribution for React website",
      },
    });
produces
Copy code
Error [ValidationError]: Circular dependency between resources:
f

Frank

03/18/2022, 8:43 AM
Let me give it a try.
So this worked for me
Copy code
import { Lazy } from "aws-cdk-lib";
import * as sst from "@serverless-stack/resources";

export class MainStack extends sst.Stack {
  constructor(scope: <http://sst.App|sst.App>, id: string) {
    super(scope, id);

    let site;

    // Create Api
    const api = new sst.Api(this, "Api", {
      cors: {
        allowOrigins: [
          "<http://localhost:3000>",
          Lazy.stringValue({
            produce() {
              return site.url;
            }
          })
        ],
      },
      routes: {
        "GET /": "src/lambda.main",
      },
    });


    // Create StaticSite
    site = new sst.ReactStaticSite(this, "Frontend", {
      path: "src/sites/react-app",
      environment: {
        REACT_APP_API_URL: api.url,
      },
    });
  }
}
After I run
sst build
, I’m able to see the CORS setting in the generated Cloudformation template
Copy code
"ApiCD79AAA0": {
      "Type": "AWS::ApiGatewayV2::Api",
      "Properties": {
        "CorsConfiguration": {
          "AllowOrigins": [
            "<http://localhost:3000>",
            {
              "Fn::Join": [
                "",
                [
                  "https://",
                  {
                    "Fn::GetAtt": [
                      "FrontendDistributionC0C89627",
                      "DomainName"
                    ]
                  }
                ]
              ]
            }
          ]
        },
a

Artemiy Davydov

03/18/2022, 9:02 AM
🤷

https://i.imgur.com/P5G7XCz.png

f

Frank

03/18/2022, 9:03 AM
I didn’t deploy. Lemme try deploying.
a

Artemiy Davydov

03/18/2022, 9:18 AM
Oh, I found the difference. You used
stringValue
instead of
string
But I don't have such a method
Copy code
Property 'stringValue' does not exist on type 'typeof Lazy'
f

Frank

03/18/2022, 9:20 AM
Deploy failed for me as well with the same circular dependency issue.
a

Artemiy Davydov

03/18/2022, 9:21 AM
But yeah, also failed with
(Lazy as any).stringValue
f

Frank

03/18/2022, 9:21 AM
I see what’s going on.
It works when the Lazy trick is used set
site.url
as a Lambda environment variable, because CloudFormation can: 1. first create the
Api
2. then create the
CloudFront site
(with Api’s endpoint) 3. and finally create the
Lambda functions
(with site’s url). https://gist.github.com/fwang/db1e5697913c5533f8b95a4f04464870
But the same Lazy trick doesn’t work for CORS settings b/c CORS is part of the
Api
setting (not the Lambda function settings). So there is a circular dependency between 1 and 2.
a

Artemiy Davydov

03/18/2022, 9:26 AM
So how do I set the url for CORS (preflight) then?
I definitely need to use the url in the API constructor, but I also have to pass the API url to the site constructor
Is there any other way I can set the environment variable for local react build after I got the API url?
f

Frank

03/18/2022, 9:29 AM
Hmm is hardcoding the frontend url an option. This is easier if u r using a custom domain for ur site, so u know the URL ahead of time.
This’d be the easiest solution I see many ppl do.
a

Artemiy Davydov

03/18/2022, 9:31 AM
Hardcoding won't help with the *.cloudfront url. I won't know it until the first deployment
Is there any way to circumvent these restrictions?
f

Frank

03/18/2022, 9:34 AM
Any solutions I can think of would require a first dummy deployment. So u either know the Api’s url or the Site’s url to break the circular dependency.
Using a custom domain for the site or api is the only way around as far as i can think of.
6 Views