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

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

Open
2 tasks done
andreygal opened this issue May 23, 2024 · 20 comments
Open
2 tasks done
Labels
bug Something isn't working

Comments

@andreygal
Copy link

Verified issue does not already exist?

  • I have searched and found no existing issue
  • I will be providing steps how to reproduce the bug (in most cases this will also mean uploading a demo budget file)

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

@andreygal andreygal added the bug Something isn't working label May 23, 2024
@VoltaicGRiD
Copy link
Contributor

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
Copy link
Author

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
Copy link

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
Copy link

Here's more specifically about the default_type module.

@andreygal
Copy link
Author

andreygal commented Jun 9, 2024 via email

@Towerism
Copy link

Towerism commented Jun 23, 2024

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
Copy link

safehome-jdev commented Jun 23, 2024

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
Copy link

Towerism commented Jun 23, 2024 via email

@Towerism
Copy link

@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
Copy link

safehome-jdev commented Jun 23, 2024

@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
Copy link

@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
Copy link

@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
Copy link

@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.

@mdelpire
Copy link

@Towerism were you able to solve the issue you have with authentik? I have the same issue. Using actual budget with Nginx Proxy Mnager and Authentik. If yes, could you explain how?
Thanks a lot

@Towerism
Copy link

@mdelpire i did not solve this directly. I’m using a workaround currently where I intercept the request to load the service worker and return a 404 not found.

@mdbell
Copy link

mdbell commented Aug 13, 2024

While not directly related to this issue, I was also able to get the "Sign Out" button to actually sign me out of Authentik by replacing registerSW.js with:

//Put the original contents of registerSW.js here if you want to preserve stock behavior

//and the hook for the Sign Out button.
(function() {
    function hookSignOutButton() {
        // Select all elements with the role of button
        const buttons = document.querySelectorAll('div[role="button"]');
        
        buttons.forEach(button => {
            const span = button.querySelector('span');
            if (span && span.textContent.trim() === 'Sign out') {
                button.addEventListener('click', () => {
                    window.location.href = '/outpost.goauthentik.io/sign_out';
                });
                console.log('Sign out button hooked');
            }
        });
    }

    function initializeObserver() {
        // the registerSW file gets run before the page is fully loaded, so we poll until document.body exists.
        // There's probably a cleaner way to do this, but I'm not a great web-developer and this works
        if (document.body) {
            const observer = new MutationObserver(() => {
                hookSignOutButton();
            });
            observer.observe(document.body, { childList: true, subtree: true });
        } else {
            setTimeout(initializeObserver, 100);
        }
    }

    hookSignOutButton();
    initializeObserver();
})();

And then using it with the following location directive in nginx:


location = /registerSW.js {
    alias /var/www/customRegisterSW.js;
}

I imagine there's a better way to do this by building actual myself, but this is a quick fix for me.

@TimQuelch
Copy link
Contributor

I've opened #3286 with a workaround for this.

@alexyao2015
Copy link

@TimQuelch

Just updated to 24.10.1 which includes #3286 and still seem to experiencing the same issue. Did some quick digging and it seems like this kcab.worker.js script is attempting to pull from /sync but it isn't reloading. Manually calling the reload code from that PR from the console does work, meaning that there are probably some other places that need to be hooked with the force reload code.

@TimQuelch
Copy link
Contributor

Just updated to 24.10.1 which includes #3286 and still seem to experiencing the same issue.

I can't seem to reproduce this. Mine is now refreshing correctly when sync is redirected. Are you sure your client has updated and is using the new version? Try clearing caches and refreshing.

@alexyao2015
Copy link

Pretty sure it was. I've since disabled the sw again using nginx rules, but I had checked the mangled sources in Firefox and could see it was the updated version.

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

No branches or pull requests

8 participants