Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CORS issue in creating origin headers #355

Open
maxdzin opened this issue Mar 8, 2023 · 2 comments · May be fixed by #870
Open

CORS issue in creating origin headers #355

maxdzin opened this issue Mar 8, 2023 · 2 comments · May be fixed by #870
Assignees
Labels
bug Something isn't working security

Comments

@maxdzin
Copy link

maxdzin commented Mar 8, 2023

Environment

Node: v18.15.0
npm: v9.5.0
Nuxt: v3.2.3 + nuxt-security v0.11.0

Reproduction

https://github.com/maxdzin/nuxt-cors-issue

Describe the bug

When working on API, I used nuxt-security and I noticed that the Access-Control-Allow-Origin header is not set correctly.

So, there could be a bug related to the just-merged @nozomuikuta/h3-cors into H3.

There is a discussion about the CORS problems in this nuxt-security issue and it is related to h3-cors things.

In short, there's a response 'Access-Control-Allow-Origin' header is not set correctly, while the options contain specific origin values.

So, there are these problems:

  1. If the H3CorsOptions.origin value is set as an array of strings or an array of RegExp, the access-control-allow-origin header is not set in this case. In order to set that response header correctly, I believe that createOriginHeaders method must return with access-control-allow-origin value set in the last return block here:
export function createOriginHeaders(
  event: H3Event,
  options: H3CorsOptions
): H3AccessControlAllowOriginHeader {
  const { origin: originOption } = options;
  const origin = getRequestHeader(event, "origin");

  if (!origin || !originOption || originOption === "*") {
    return { "access-control-allow-origin": "*" };
  }

  if (typeof originOption === "string") {
    return { "access-control-allow-origin": originOption, vary: "origin" };
  }

  return isCorsOriginAllowed(origin, options)
    ? { "access-control-allow-origin": origin, vary: "origin" }
    : {};
}

So, if the isCorsOriginAllowed is false, it returns an empty object. But, it must be set by originOption value instead.

  1. And there's confusion with H3CorsOptions type:
export interface H3CorsOptions {
  origin?: "*" | "null" | (string | RegExp)[] | ((origin: string) => boolean);
  ...

The string type is missing there.

Additional context

No response

Logs

No response

@nozomuikuta
Copy link
Member

@maxdzin

Thank you for reporting the issue.

  1. If the H3CorsOptions.origin value is set as an array of strings or an array of RegExp, the access-control-allow-origin header is not set in this case. In order to set that response header correctly, I believe that createOriginHeaders method must return with access-control-allow-origin value set in the last return block here:

Yes, it seems to be a bug, and CORS handler should return some specific access-control-allow-origin when the request origin is not allowed. Otherwise, as far as I see your reproduction, the app behaves like being set access-control-allow-origin header as * (allow CORS from any origin).

return isCorsOriginAllowed(origin, options)
  ? { "access-control-allow-origin": origin, vary: "origin" }
  : {};

Then, although this is a design mistake that I inherited from Express CORS middleware, we have to provide a way to set the specific access-control-allow-origin to fix this issue, especially when origin option is a RegExp, an array of string and/or RegExp, or a function that currently returns boolean.

  1. And there's confusion with H3CorsOptions type:

This is my design as the original author of the feature. I wanted to treat "*" and "null" as special cases and wanted developers to be aware of them apart from specific origins, and also wanted to handle the former apart from the latter in the code.

But, thinking in again, it would be more convenient if developers can specify the only one allowed origin as an unwrapped string or RegExp.


To avoid breaking changes, how about the following hot-fix?

// As-Is
export interface H3CorsOptions {
  origin?: 
    | "*" 
    | "null"
    | (string | RegExp)[]
    | ((origin: string) => boolean)
}

// To-Be
export interface H3CorsOptions {
  origin?: 
    | "*" 
    | "null"
    | string // (1)
    | [RegExp, string]  // (2)
    | (string | RegExp | [RegExp, string])[]  // (3)
    | ((origin: string) => boolean | string)  // (4)
}
  1. Single string type is newly added for convenience
  2. Single RegExp is newly supported, but with origin string when request origin doesn't match
  3. [RegExp, string] is newly supported as (2). Add deprecate/security warning for unwrapped RegExp usage.
  4. string return type is newly added to set access-control-allow-origin in case request origin is not allowed. Add deprecate/security warning when the function returns false.

@pi0

Sorry to bother you, but do you have any thoughts on this issue? My idea above is based on my guess that you don't want to introduce breaking changes and bump up to new major version. But, if possible, there would be more sophisticated API.

@maxdzin

Any feedback from you is also welcome.

@maxdzin
Copy link
Author

maxdzin commented Mar 9, 2023

@nozomuikuta great! That should work.

I'm thinking about one more thing in the createOriginHeaders method... If originOption is set as an array of strings or an array of RegExps, that must set access-control-allow-origin header by the request origin that matches some of originOption, that's clear, but if it is not matched, then it shouldn't set the header. Maybe that's the correct behavior.

For me, it was not clear how to set the originOption if I require an exact value to be matched (since as of type, it accepts string[], but not string). But I think that if a string will be added as one of the possible types, it should solve the issue.
And, IMO, there must be some explanation regarding originOption types. At least for string[], RegExp, and RegExp[] - it is not so obvious how it is about to be considered and used to check/set the access-control-allow-origin header in this case. So, adding some short explanations of those types would help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working security
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants