actualbudget / actual

A local-first personal finance app
https://actualbudget.org
MIT License
12.55k stars 960 forks source link

[Bug]: Service Worker preventing correct execution of bootstrap when using NGINX reverse proxy #2793

Open andreygal opened 1 month ago

andreygal commented 1 month ago

Verified issue does not already exist?

What happened?

Thank you for developing this fantastic project! However, I've encountered a problem with the service worker when passing the connection through an nginx reverse proxy. When the server is accessed directly, everything functions without issues. However, when the connection is proxied, the resources load, but the loading script fails to start. If the sw.js is unregistered, the page then loads correctly. image image

Where are you hosting Actual?

Docker

What browsers are you seeing the problem on?

Chrome, Microsoft Edge, Desktop App (Electron), Other

Operating System

Windows 10

VoltaicGRiD commented 1 month ago

I was just having a similar issue setting up my reverse proxy for a VPN tunnel for my own setup, can you share a redacted copy of your NGINX configuration file for this site (and the regular nginx.conf file that you have if you have modified it from the default). I'm curious enough to work on finding a solution for this as a new contributor (if at all possible).

andreygal commented 1 month ago

***nginx.conf** user nginxuser; worker_processes 1;

error_log /var/log/nginx/error.log; error_log /var/log/nginx/error.log notice; error_log /var/log/nginx/error.log info;

pid /run/nginx.pid;

events { worker_connections 1024; }

http { default_type application/octet-stream; server_tokens off; add_header X-Frame-Options "SAMEORIGIN";

log_format  main  '$remote_addr - $remote_port - $remote_user [$time_local] "$request" '
                  '$status $body_bytes_sent "$http_referer" '
                  '"$http_user_agent" "$http_x_forwarded_for" '
                  '"$host" "$http_x_real_ip" "$http_x_forwarded_proto" $uri';

sendfile        on;
#tcp_nopush     on;

#keepalive_timeout  0;
keepalive_timeout  65;

#gzip  on;

include mime.types;
include /etc/nginx/conf.d/ssl_settings.conf;

server {
    listen       80;
    server_name  my_ip;
    return 301 https://$host$request_uri; 
}

}

**ssl_settings.conf*****

server { listen [::]:443 ssl; listen 443 ssl; http2 on; server_name actual.myserver.com;

# Include common subdomain configuration
include /etc/nginx/conf.d/subd_serv.conf;

location / {
    # Set headers to maintain original request information
    proxy_pass https://mtls.myserver.com:port;
    proxy_set_header Host $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;

    # SSL settings for the upstream server (required)
    proxy_ssl_certificate         /etc/x_client_cert.pem;
    proxy_ssl_certificate_key     /etc/x_client_key.pem;
    proxy_ssl_trusted_certificate /etc/ca.pem;
    proxy_ssl_verify on;
    proxy_ssl_verify_depth 2;
    proxy_ssl_server_name on;
}

}

Mutual TLS redirect for Actual

server { listen [::]:port ssl; listen port ssl; http2 on; server_name mtls.myserver.com;

# Enable Diffie-Hellman Key Exchange 
ssl_dhparam /etc/nginx/ssl/ssl-dhparams.pem;

# Internal self-signed certs
ssl_certificate     cert_path
ssl_certificate_key key_path;

# mTLS requirements 
ssl_client_certificate /etc/ca.pem;
ssl_verify_client on;

# Additional security measures 
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers on;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;

# redirect server error pages to the static page /50x.html
error_page   500 502 503 504  /50x.html;

location / {
    # Set headers to maintain original request information
    proxy_pass https://upstream.home:someport;
    proxy_set_header Host $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;
}

}

safehome-jdev commented 3 weeks ago

***nginx.conf** [...]

http {

default_type  application/octet-stream;

[...]

Is there a particular requirement for predetermining the default Content-Type header? It could be this is inferring a different Content-Type and causing your headaches

safehome-jdev commented 3 weeks ago

Here's more specifically about the default_type module.

andreygal commented 3 weeks ago

Thank you so much for taking a look. Will try to implement and see if it helps.

On June 8, 2024 9:15:30 PM EDT, safehome-jdev @.***> wrote:

Here's more specifically about the default_type module.

-- Reply to this email directly or view it on GitHub: https://github.com/actualbudget/actual/issues/2793#issuecomment-2156255269 You are receiving this because you authored the thread.

Message ID: @.***>

Towerism commented 1 week ago

I have a similar problem. My problem is that I am reverse proxying and using authentik as SSO. When authentik login session expires, actual breaks, I suppose because the service worker bypasses the reverse proxy.

I got around this, by updating my reverse proxy with a directive that responds to the path /registerSW.js with a 404. This prevents the service worker from ever starting.

Obviously this is not ideal, as now actual will not work without an internet connection. But at least without the service worker, the application will redirect me to my SSO instead of breaking.

safehome-jdev commented 1 week ago

I have a similar problem. My problem is that I am reverse proxying and using authentik as SSO. When authentik login session expires, actual breaks, I suppose because the service worker bypasses the reverse proxy.

I got around this, by updating my reverse proxy with a directive that responds to the path /registerSW.js with a 404. This prevents the service worker from ever starting.

Obviously this is not ideal, as now actual will not work without an internet connection. But at least without the service worker, the application will redirect me to my SSO instead of breaking.

@Towerism Are you able to post your NGINX config for this? As well as any browser errors/failure responses from your browser's console and network tools for when a login expires?

A quick search shows that workers do not retry upon failure, which checks out so far.

Towerism commented 1 week ago

Sure I’d be happy to. ’m using caddy which has a lot going on in it due to other stuff that I’m hosting. I’ll take some time to put together a more minimal docker compose and caddy file and share it here.

On Sun, Jun 23, 2024 at 01:32 safehome-jdev @.***> wrote:

I have a similar problem. My problem is that I am reverse proxying and using authentik as SSO. When authentik login session expires, actual breaks, I suppose because the service worker bypasses the reverse proxy.

I got around this, by updating my reverse proxy with a directive that responds to the path /registerSW.js with a 404. This prevents the service worker from ever starting.

Obviously this is not ideal, as now actual will not work without an internet connection. But at least without the service worker, the application will redirect me to my SSO instead of breaking.

@Towerism https://github.com/Towerism Are you able to post your NGINX config for this?

— Reply to this email directly, view it on GitHub https://github.com/actualbudget/actual/issues/2793#issuecomment-2184678334, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAPWJUUQV45ORBLNIVFTVXDZIZTWDAVCNFSM6AAAAABIF4CKOWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCOBUGY3TQMZTGQ . You are receiving this because you were mentioned.Message ID: @.***>

Towerism commented 1 week ago

@safehome-jdev, when my auth endpoint is queried via fetch, it is giving cors errors. So it seems that the error is something I should be able to work out on the authentik configuration side.

safehome-jdev commented 1 week ago

@safehome-jdev, when my auth endpoint is queried via fetch, it is giving cors errors. So it seems that the error is something I should be able to work out on the authentik configuration side.

Ah, you'll need to add something like the following to allow auth requests from your domain name to Authentik's auth servers:

location / {
  add_header 'Access-Control-Allow-Origin' '*';
  add_header 'Access-Control-Allow-Methods' 'GET, POST';
}

Change the asterisk in Access-Control-Allow-Origin to your referrer header from your request headers in your browser's network tools. That should get you what you need 👍🏼

Towerism commented 1 week ago

@safehome-jdev I could try that, but I believe what's going on is that the fetch to /authorize results in a redirect, so the preflight request has ERR_INVALID_REDIRECT. So authentik is expecting the application to navigate to the authorize url rather than make an XHR request. I think?

Towerism commented 1 week ago

@safehome-jdev i was able to get caddy to handle cors properly. However, this didn't fix the issue that with authentik configured in caddy using forward_auth, actual tries to use an XHR request instead of navigating.

Error from service worker in the console:

SyntaxError: Unexpected token '<', "

<!DOCTYPE "... is not valid JSON
    at JSON.parse (<anonymous>)
    at m9.subscribe-get-user (kcab.worker.9402297f261e3b047066.js:249:5268)
Towerism commented 1 week ago

@safehome-jdev I just realized what's happening. i have caddy configured to redirect to authentik. the service worker is serving the page, completely bypassing caddy. in this case, the service worker tries to authenticate with actual-server but is redirected to authentik instead since the cookie expired. I think I just have to disable the service worker in my case in order to have authentik work correctly.