bastienwirtz / homer

A very simple static homepage for your server.
https://homer-demo.netlify.app/
Apache License 2.0
8.91k stars 770 forks source link

Improve docs with better CORS handling #396

Open Roundaround opened 2 years ago

Roundaround commented 2 years ago

We get a lot of issues reported and questions in the Gitter chat about errors relating to CORS. We should mention the potential issue with CORS more prominently in the docs (i.e. early on in the custom services intro) and provide people with a better solution. The recommended proxy servers haven't been touched in years and I personally could not get them to work. Writing one is not super complicated so maybe it's time we provide an official solution. The one I use in my personal deployment looks like this:

import express, { json } from 'express';
import cors from 'cors';
import axios from 'axios';

const app = express();

app.use(json());
app.use(cors());

app.get('*', async (req, res, next) => {
  try {
    let target = req.originalUrl;
    if (target.startsWith('/')) {
      target = target.substring(1);
    }

    console.log(`Proxying request meant for '${target}'`);
    const response = await axios.get(target, {headers: req.headers});
    return res.json(response.data)
  } catch (err) {
    return res.status(500).send(err);
  }
});

console.log('App listening on port 3333');
app.listen(3333);
espilioto commented 2 years ago

For folks behind Traefik this is my config:

We define a middleware called cors-headers in config.yml:

    cors-headers:
      headers:
        accessControlAllowMethods:
          - GET
          - OPTIONS
          - PUT
        accessControlAllowOriginList:
          - https://homer.url.here
        accessControlAllowHeaders: ["x-api-key, Origin, Authorization"]
        accessControlAllowCredentials: true
        accessControlMaxAge: 100
        addVaryHeader: true

The accessControlAllowHeaders might need some tweaking depending on the services behind Traefik; reading this will eventually make sense 🥲

The middleware can then be assigned to routers with something like this in your docker-compose in each image: "traefik.http.routers.myservice.middlewares=cors-headers@file" DON'T FORGET THE SECURE ROUTER!! "traefik.http.routers.myservice-secure.middlewares=cors-headers@file"

To verify it works you can inspect it from the dashboard: image

HeyItsJono commented 2 years ago

For anyone looking for something easily deployable in the meantime whilst awaiting a solution, as a shameless self-plug you could try PyCors.

KibosJ commented 2 years ago

If using SWAG I got mine working by creating a cors.conf in the nginx folder

set $cors '';
if ($http_origin ~ '^https?://(localhost|homer.domain\.com)') {
        set $cors 'true';
}

if ($cors = 'true') {
        add_header 'Access-Control-Allow-Origin' "$http_origin" always;
        add_header 'Access-Control-Allow-Credentials' 'true' always;
        add_header 'Access-Control-Allow-Methods' 'GET,OPTIONS,PUT' always;
        add_header 'Access-Control-Allow-Headers' 'X-Api-Key,Origin,Authorization,X-Requested-With,Content-Type,Accept,Access-Control-Request-Method' always;
        add_header 'Access-Control-Expose-Headers' 'Authorization' always;
}

if ($request_method = OPTIONS ) {
        add_header 'Access-Control-Allow-Origin' "$http_origin" always;
        add_header 'Access-Control-Allow-Headers' 'X-Api-Key,Origin,Authorization,X-Requested-With,Content-Type,Accept,Access-Control-Request-Method' always;
        return 200;
}

Then adding this to each server conf (Portainer, Sonarr, etc.)

proxy_hide_header 'Access-Control-Allow-Origin';
include /config/nginx/cors.conf;

Sonarr's api block in SWAG would become:

location ~ (/sonarr)?/api {
        include /config/nginx/proxy.conf;
        include /config/nginx/resolver.conf;
        set $upstream_app sonarr;
        set $upstream_port 8989;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
        proxy_hide_header 'Access-Control-Allow-Origin';
        include /config/nginx/cors.conf;
   }
wardwygaerts commented 2 years ago

I have it working in Traefik. But I also use forwardAuth (Google authentication) and when I enable this middleware together with the one for cors, it's not working anymore: Access to fetch at 'https://accounts.google.com/o/oauth2/auth?client_id=xxxxxx.apps.googleusercontent.com&prompt=select_account&redirect_uri=https%3A%2F%2Foauth.domain.com%2F_oauth&response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email&state=xxxxxf%3Agoogle%3Ahttps%3A%2F%2Fportainer.domain.com%2Fapi%2Fendpoints' (redirected from 'https://portainer.domain.com/api/endpoints') from origin 'https://portal.domain.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Do I need to modify some additional stuff?

espilioto commented 2 years ago

Hmmm, examine the response with and without it, maybe you must allow a specific header through.

wardwygaerts commented 2 years ago

How can I find out missing headers? I always check via Dev Tools and then Console.

espilioto commented 2 years ago

Sure, that's how I'd go about it too. If for some reason something feels funky, just try doing the same thing without the proxy; I usually just start the same image in a different docker network to make sure everything works as it should without it and then examine the headers.

wardwygaerts commented 1 year ago

I thought I solved it in the past (everything is working for Sonarr, Radarr & Prowlarr). But I keep getting the cors messages for Portainer, Uptime-Kuma and Paperless.

Access to fetch at 'https://portainer.*******.com/api/endpoints' from origin 'https://homer.**********.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

image

I used this middleware in Traefik v2.6.6 (a '*' for accessControlAllowOriginList for testing):

cors-headers:
      headers:
        accessControlAllowMethods:
          - GET
          - OPTIONS
          - PUT
        accessControlAllowOriginList: "*"
        accessControlAllowHeaders: ["X-Api-Key,Origin,Authorization,X-Requested-With,Content-Type,Accept,Access-Control-Request-Method"]
        accessControlAllowCredentials: true
        accessControlMaxAge: 100
        addVaryHeader: true

I just figured out that when I remove my oauth middleware, it works with the cors-header. oauth is a container which handles Google authentication. Any ideas?

middlewares-oauth:
      forwardAuth:
        address: "http://oauth:4181"
        trustForwardHeader: true
        authResponseHeaders:
          - "X-Forwarded-User"
XxAcielxX commented 1 year ago

if ($http_origin ~ '^https?://(localhost|homer.domain.com)') {

@KibosJ Are you hosting Homer on a subdomain? What changes are needed if I have Homer on my main domain (not on subdomain)?

agoodshort commented 1 year ago

This answer from @espilioto was extremely useful to handle CORS issues with Traefik. Any chance we can get that somewhere in the documentation? The official traefik documentation for CORS headers could be referenced as well.

asyba commented 1 year ago

For anyone looking for something easily deployable in the meantime whilst awaiting a solution, as a shameless self-plug you could try PyCors.

how did you make it work? it doest work for me, lots of error while doing GET to http://192.168.1.114:5757/http://192.168.1.114:3001/status/default

192.168.1.124 - - [24/Jan/2023 01:06:17] "GET /assets/index.5612bb23.css HTTP/1.1" 500 -
[2023-01-24 01:06:17,545] ERROR in app: Exception on /icon.svg [GET]
Traceback (most recent call last):
  File "/usr/local/lib/python3.10/site-packages/flask/app.py", line 2077, in wsgi_app
    response = self.full_dispatch_request()
Chiller2019 commented 1 year ago

Anyone has this for NGINX Proxy Manager?

needs-coffee commented 1 year ago

For anyone else having issues with CORS requests for services being proxied by nginx this might help (for me the issue was with Portainer but the issue will apply with any service proxied by nginx).

The preflight request response was not including the correct headers as nginx was responding to the preflight OPTIONS request with a 405 status, which did not include the Access-Control-Allow headers.

The solution for me was to add a block for responding to an options request with appropriate headers.

        location / {
                if ($request_method = OPTIONS) {
                        add_header Access-Control-Allow-Origin 'homer.server.lan';
                        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
                        add_header Content-Type text/plain;
                        add_header Access-Control-Allow-Headers 'origin, x-requested-with, accept, x-api-key';
                        add_header Content-Length 0;
                        return 204;
                }
                add_header              Access-Control-Allow-Origin 'homer.server.lan';
                add_header              Access-Control-Allow-Headers 'origin, x-requested-with, accept, x-api-key';
                proxy_set_header        Host $host;
                add_header              X-Served-By $host;
                proxy_set_header        X-Real-IP $remote_addr;
                proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header        X-Forwarded-Proto $scheme;
                proxy_set_header        X-Forwarded-Scheme $scheme;
                proxy_pass              http://localhost:9000/;
                proxy_read_timeout      90;
                proxy_set_header        Upgrade $http_upgrade;
                proxy_set_header        Connection $connection_upgrade;
                proxy_http_version      1.1;
                proxy_redirect          http://localhost:9000 https://$host;
        }
speedyconzales commented 10 months ago

actually this works for portainer -> https://github.com/bulletmark/corsproxy but not for the other services like Tautulli... for them I use -> https://hub.docker.com/r/saschabrockel/cors-container Don't ask me why. Instead I would appreciate if someone could explain to me why I need two different ones in order to get all services up and running?!

So in my case as of now. A simple guide in order to direct me to these two would be sufficient. Now everything works as expected in my case. Even portainer

Ashkaan commented 10 months ago

Does anyone know how to get Homer working with HAProxy via PFsense?

Fox51 commented 9 months ago

Anyone has this for NGINX Proxy Manager?

For me, configuring a redirection port trough HTTPS works, using an SSL of NGINX Proxy Manager, first you need con configure VIRTUAL_PORT in the environment file of variables, usually it uses port 9000, then you need to export or tell in your configuration that you are going to use the PORT 9000 for HTTP only, from this you just configure your proxy host in Custom Locations tab -> add location then configure with: location field: / Scheme field: http Forward Hostname / IP field : your.portainer.ip Forward Port filed : 9000 Then you need to click the icon that appears like a gear beside the field "location", a text area will pop up below and add the next text: add_header Access-Control-Allow-Origin ; (also, you could specify the domain or IP from where your Homer Dashboard is instead of the '' )

Hope this helps others with the same problem with NGINX Proxy Manager

muzzymate commented 9 months ago

actually this works for portainer -> https://github.com/bulletmark/corsproxy but not for the other services like Tautulli... for them I use -> https://hub.docker.com/r/saschabrockel/cors-container Don't ask me why. Instead I would appreciate if someone could explain to me why I need two different ones in order to get all services up and running?!

So in my case as of now. A simple guide in order to direct me to these two would be sufficient. Now everything works as expected in my case. Even portainer

Thank you for pointing me towards the Cors-container! Everything was going over my head until I looked into that one and finally got the Tautulli integration working.