Giter VIP home page Giter VIP logo

elysia-cors's Introduction

@elysiajs/cors

Plugin for elysia that for Cross Origin Requests (CORs)

Installation

bun add @elysiajs/cors

Example

import { Elysia } from 'elysia'
import { cors } from '@elysiajs/cors'

const app = new Elysia()
    .use(cors())
    .listen(8080)

Config

origin

@default true

Assign the Access-Control-Allow-Origin header.

Value can be one of the following:

  • string - String of origin which will directly assign to Access-Control-Allow-Origin

  • boolean - If set to true, Access-Control-Allow-Origin will be set to * (accept all origin)

  • RegExp - Pattern to use to test with request's url, will accept origin if matched.

  • Function - Custom logic to validate origin acceptance or not. will accept origin if true is returned.

    • Function will accepts Context just like Handler
    // Example usage
    app.use(cors, {
        origin: ({ request, headers }) => true
    })
    
    // Type Definition
    type CORSOriginFn = (context: Context) => boolean | void
  • Array<string | RegExp | Function> - Will try to find truthy value of all options above. Will accept Request if one is true.

methods

@default *

Assign Access-Control-Allow-Methods header.

Value can be one of the following: Accept:

  • undefined | null | '' - Ignore all methods.

  • * - Accept all methods.

  • HTTPMethod - Will be directly set to Access-Control-Allow-Methods.

    • Expects either a single method or a comma-delimited string (eg: 'GET, PUT, POST')
  • HTTPMethod[] - Allow multiple HTTP methods.

    • eg: ['GET', 'PUT', 'POST']

allowedHeaders

@default *

Assign Access-Control-Allow-Headers header.

Allow incoming request with the specified headers.

Value can be one of the following:

  • string

    • Expects either a single method or a comma-delimited string (eg: 'Content-Type, Authorization').
  • string[] - Allow multiple HTTP methods.

    • eg: ['Content-Type', 'Authorization']

exposedHeaders

@default *

Assign Access-Control-Exposed-Headers header.

Return the specified headers to request in CORS mode.

Value can be one of the following:

  • string

    • Expects either a single method or a comma-delimited string (eg: 'Content-Type, 'X-Powered-By').
  • string[] - Allow multiple HTTP methods.

    • eg: ['Content-Type', 'X-Powered-By']

credentials

@default true

Assign Access-Control-Allow-Credentials header.

Allow incoming requests to send credentials header.

  • boolean - Available if set to true.

maxAge

@default 5

Assign Access-Control-Max-Age header.

Allow incoming requests to send credentials header.

  • number - Duration in seconds to indicates how long the results of a preflight request can be cached.

preflight

@default true

Add [OPTIONS] /* handler to handle preflight request which response with HTTP 204 and CORS hints.

  • boolean - Available if set to true.

elysia-cors's People

Contributors

arthurfiorette avatar bogeychan avatar danadajian avatar saltyaom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

elysia-cors's Issues

Typescript Typing issue

new Elysia()
    .use(
      logger({
        level: 'info',
      })
    )
    .use(
      cors({
        origin: /.*\.saltyaom\.com$/,
      })
    )
    .use(swagger())
    .get('/', () => 'Hello World')
    .get(
      '/finish-match',
      async ({ query }) => {
        const gain = 50
        const result = await addExpToUser(query.userId, gain)
        const userDB = result[0]
        const totalExp = (userDB.exp || 0) + gain

        const channel = guild.channels.cache.find(
          (x) => x.id === '1216638376023818350' // game channel
        ) // bot -test

        if (channel?.type === ChannelType.GuildText) {
          channel.send({
            content: `${userDB.name} baru menyelesaikan 1 match. +50 EXP`,
          })
        }
      },
      {
        query: t.Object({
          userId: t.String(),
          roomId: t.String(),
        }),
      }
    )

Adding

    .use(
      cors({
        origin: /.*\.saltyaom\.com$/,
      })
    )

messed up the typescript typing. Removing it, fixed the issue. Let me know if there's any other info I can provide

image
{
  "name": "@oneayah/simple-backend",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "bun --watch server.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@bogeychan/elysia-logger": "^0.0.17",
    "@elysiajs/cors": "^1.0.2",
    "@elysiajs/swagger": "^0.8.5",
    "@oneayah/auth": "workspace:*",
    "@oneayah/db": "workspace:*",
    "@sinclair/typebox": "^0.32.15",
    "arctic": "^1.2.1",
    "discord.js": "^14.14.1",
    "drizzle-orm": "^0.29.3",
    "elysia": "^0.8.17"
  },
  "devDependencies": {
    "@types/pg": "^8.10.9"
  }
}

Could not assign string or regex value for `origin`

Hi, how can I resolve this CORS error? When set as a string or regex, this will show an error that says the origin prop is an empty string.

Response to preflight request doesn't pass access control check: The 'Access-Control-Allow-Origin' header contains the invalid value ''. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

But when I use other approaches like boolean values or customize logic with functions, it will cause:

No 'Access-Control-Allow-Origin' header is present on the requested resource.

This is my current usage:

new Elysia()
  .use(
    cors({
      origin: ({ headers }) => {
        const origin = headers.get("origin");
        return origin === ALLOWED_ORIGIN;
      },
      allowedHeaders: ["Content-Type", "Accept", "Authorization"],
      methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
    }),
  )
  .group("/api", (app) => app.use(auth).use(user))
  .listen(3000);

{"name":"TypeError","message":"Response constructor: Invalid response status code 204"} is thrown when browser is making a OPTIONS preflight request to an Elysia server running on Node.js

Node.js’s Response constructor throws an error when response body is not null (e.g. empty string):

new Response('', { status: 204 })
/*
Uncaught TypeError: Response constructor: Invalid response status code 204
    at webidl.errors.exception (node:internal/deps/undici/undici:1636:14)
    at initializeResponse (node:internal/deps/undici/undici:5754:31)
    at new Response (node:internal/deps/undici/undici:5548:9)
*/

This is what @elysiajs/cors does:

Solution is to set body to null instead of ''.

Dependency versions

    "@elysiajs/cors": "1.0.0-beta.0",
    "elysia": "1.0.0-beta.1",

DELETE method not allowed by default

The plugin's documentation specifies that the default allowed methods is basically the * wildcard. However when developing my API with this plugin I encountered CORS issues when trying to call a route using the DELETE method:

import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";

import * as gql from "./gql";
import * as media from "./media";

new Elysia()
	.use(cors())
	.group("/api", (app) =>
		app
			.post("/gql", gql.POST)
			.group("/media", (app) =>
				app
					.get("", media.GET)
					.post("", media.POST)
					.delete("", media.DELETE), // This one
			),
	)
	.listen(3000);

Fixed it by setting the methods parameter to *:

import { Elysia } from "elysia";
import { cors } from "@elysiajs/cors";

import * as gql from "./gql";
import * as media from "./media";

new Elysia()
	.use(cors({ methods: "*" })) // Here
	.group("/api", (app) =>
		app
			.post("/gql", gql.POST)
			.group("/media", (app) =>
				app
					.get("", media.GET)
					.post("", media.POST)
					.delete("", media.DELETE),
			),
	)
	.listen(3000);

Not a huge problem but kinda annoying

turn on noUncheckedIndexedAccess TS rule

When setting noUncheckedIndexedAccess to true in a project consuming this library, type errors ensue:

node_modules/@elysiajs/cors/src/index.ts:211:49 - error TS2345: Argument of type 'Origin | undefined' is not assignable to parameter of type 'Origin'.
  Type 'undefined' is not assignable to type 'Origin'.

211                     const value = processOrigin(origins[i], request, from)
                                                    ~~~~~~~~~~

Preflight request gets denied when settings should allow it

The example code underneath should fetch cookies from my backend thats on a different origin than my front end. However i get a error "Access to fetch at 'http://localhost:1234/cookies' from origin 'http://127.0.0.1:5500' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response." However if i understand the doc correct elysia-cors should allow it by default.

This is a example i made to narrow down the problem
Frontend
Open with live server - VS code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="post">
        <input type="text" placeholder="username" name="username">
        <input type="text" placeholder="password" name="password">
        <button>submit</button>
    </form>
    <script>
        document.querySelector("form").addEventListener("submit", (e) => {
            e.preventDefault();
            console.log("submitting");
            document.cookie = `username=${document.querySelector("input[name='username']").value}`;
            document.cookie = `password=${document.querySelector("input[name='password']").value}`;

            fetch("http://localhost:1234/cookies", {
                method: "GET",
                mode: "cors",
                headers: {
                    'Content-Type': 'application/json'
                },
                credentials: "include"
            })
            .then((response) => {
                console.log(response);
            });
        })
    </script>
</body>
</html>

Backend

import Elysia from "elysia";
import cors from "@elysiajs/cors";

const app = new Elysia();

app.use(
    cors({
        origin: true,
        methods: "*",
        allowedHeaders: "*",
        exposedHeaders: "*",
        credentials: true,
        maxAge: 50000,
        preflight: true,
    })
);

app.get("/cookies", ({ cookie: { username, password, icecream } }) => {
    icecream.value = "chocolate";
    console.log(username + ":" + password);
    return "you got me!";
});

app.listen(1234);
console.log("Server up and running at http://localhost:1234");

In addition to whats is shown here i have tried

  • app.options("*", cors());
  • To use firefox (my default is chrome )
  • Fetching the local network address

Dependancies
─ @elysiajs/[email protected]
├── [email protected]
└── [email protected]

response from OPTIONS returns 204 status code by default

elysia: 0.7.12
nodejs: 18.14
@bogeychan/elysia-polyfills: 0.6.1
browser: Google Chrome Version 116.0.5845.187 (Official Build) (x86_64)

Reading MDN docs about OPTIONS gives an example that uses 204 as the response status code, other browser implementations expect OPTIONS response to return status code of ok or 200 as mentioned in the same document.

Furthermore RFC9110 section 15.3.1 states that a response status code of 200 can be used for OPTIONS response. With that said a CORS error pops up when using default elysia-cors options.

import { Elysia } from 'elysia'

const routes = new Elysia()
  .use(cors())
  .post('/test', async ({ body }) => {
    console.log(body)
    return {}
  })

When sending a post request to /test, the browser receives a response status of 204 and decides to block the request due to CORS policy:

Access to XMLHttpRequest at 'https://api.website.com/test' from origin 'https://app.website.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

Also receives a TypeError:

TypeError: Response constructor: Invalid response status code 204
-- at webidl.errors.exception (node:internal/deps/undici/undici:1265:14)
-- at initializeResponse (node:internal/deps/undici/undici:6845:31)
-- at new Response (node:internal/deps/undici/undici:6654:9)

There's a work around with this by using Elysia instance's .options method:

import { Elysia } from 'elysia'

const routes = new Elysia()
  .use(cors())
  .options('/test', ({ set }) => {
    set.status = "OK"
  })
  .post('/test', async ({ body }) => {
    console.log(body)
    return {}
  })

It works but it is quite cumbersome. What do you guys think? Perhaps we should we default to status 200 instead.

CORSConfig doesn't work with Origin[]

Setting up cors with multiple origin doesn't work it returns multiple Access-Control-Allow-Origin when it should only return caller

Access to fetch at 'http://localhost:3000/experiences' from origin 'http://127.0.0.1:5173' has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values 'http://127.0.0.1:5173, http://localhost:4000', but only one is allowed. Have the server send the header with a valid value, or, if an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
@elysiajs/cors@^0.7.0": 
  version "0.7.0"
  resolved "https://registry.npmjs.org/@elysiajs/cors/-/cors-0.7.0.tgz"
  integrity sha512-BOR7FyIdE5WE+LzFBSOY/yTnl8+56UiIaSanQr61gL1UGbqsNRqR+L1XOV7GPMsxiWBY+m/1nnXYlIvAZ1NmWg==
  
elysia@^0.7.12: 
  version "0.7.12"
  resolved "https://registry.npmjs.org/elysia/-/elysia-0.7.12.tgz"
  integrity sha512-2dHTLgrkWdgt81zJvJD6nMWDXp77NcU/L80nIl8VjzQVtkvPQ26/dbEze+XMQ8stKcI4cv5f+S02JV3rV67DmA==
  dependencies: 
    "@sinclair/typebox" "^0.31.15"
    cookie "^0.5.0"
    cookie-signature "^1.2.1"
    eventemitter3 "^5.0.1"
    fast-querystring "^1.1.2"
    memoirist "0.1.4"
    openapi-types "^12.1.3"

CORS issue on chrome

When using fetch with credentials to make a request to a separated backend (other url/port), everything works perfectly with Firefox with default configuration (use(cors()))

On Chrome, it seems we need to specify all the default options for it to works:

use(cors({
        credentials: true,
        origin: /localhost.*/,
        allowedHeaders: ['Content-Type', 'Authorization'],
 }))

Any ideas what is causing this?

[BUG] Access-Control-Allow-Origin returns empty string when origin is set to url string

Sample use case, considering we want to limit origin to a specific client app url. One would use cors plugin like the following:

const app = new Elysia()
  .use(cors({
    origin: 'http://localhost:5173'
  }))
  .get('/', () => {
    return 'Oh hi!'
  })

Expected to have Access-Control-Allow-Origin equal to the app url which is http://localhost:5173. But the cors plugin returns empty string instead:
image

Header field Access-Control-Expose-Header is not correctly set due to typo and access of wrong variables

Due to a typo the middleware is setting the header value Access-Control-Exposed-Headers, it should be Access-Control-Expose-Headers, instead. Without the d. See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers

set.headers['Access-Control-Exposed-Headers'] =

Additionally the exposedHeader field is checked in order to set the allowed headers:

elysia-cors/src/index.ts

Lines 251 to 252 in 41e3465

if (exposedHeaders.length)
set.headers['Access-Control-Allow-Headers'] =

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.