binwiederhier / ntfy

Send push notifications to your phone or desktop using PUT/POST
https://ntfy.sh
Apache License 2.0
17.93k stars 698 forks source link

Authentication issue - 40301 "Error publishing notification" #991

Open geekykant opened 9 months ago

geekykant commented 9 months ago

:lady_beetle: Describe the bug I have setup authentication in ntfy to strictly allow only registered users to use the platform. However publishing a notification throws 403 HTTP error.

$ curl -u myusername:mypass -d "testing message" https://<base-url>/mytopic
{"code":40301,"http":403,"error":"forbidden","link":"https://ntfy.sh/docs/publish/#authentication"}

Here is my config: docker-compose.yml

version: "3"

services:
  ntfy:
    image: binwiederhier/ntfy
    container_name: ntfy-server
    command:
      - serve
    environment:
      NTFY_BASE_URL: <base-url>
      NTFY_UPSTREAM_BASE_URL: https://ntfy.sh
      NTFY_CACHE_FILE: /var/lib/ntfy/cache.db
      NTFY_AUTH_FILE: /var/lib/ntfy/auth.db
      NTFY_AUTH_DEFAULT_ACCESS: deny-all
      NTFY_BEHIND_PROXY: 1
      NTFY_ATTACHMENT_CACHE_DIR: /var/lib/ntfy/attachments
      NTFY_WEB_PUSH_FILE: /var/lib/ntfy/webpush.db
      NTFY_ENABLE_LOGIN: 1
      NTFY_ENABLE_SIGNUP: 1
    volumes:
      - /var/cache/ntfy:/var/cache/ntfy
      - /etc/ntfy:/etc/ntfy
      - ./:/var/lib/ntfy
    ports:
      - 2000:80
    healthcheck: # optional: remember to adapt the host:port to your environment
        test: ["CMD-SHELL", "wget -q --tries=1 http://localhost:80/v1/health -O - | grep -Eo '\"healthy\"\\s*:\\s*true' || exit 1"]
        interval: 60s
        timeout: 10s
        retries: 3
        start_period: 40s
    restart: unless-stopped

nginx config

server {
    if ($host = <base-url>) {
        return 301 https://$host$request_uri;
    } # managed by Certbot

  listen 80;
  server_name <base-url>;

  location / {
    # Redirect HTTP to HTTPS, but only for GET topic addresses, since we want
    # it to work with curl without the annoying https:// prefix
    set $redirect_https "";
    if ($request_method = GET) {
      set $redirect_https "yes";
    }
    if ($request_uri ~* "^/([-_a-z0-9]{0,64}$|docs/|static/)") {
      set $redirect_https "${redirect_https}yes";
    }
    if ($redirect_https = "yesyes") {
      return 302 https://$http_host$request_uri$is_args$query_string;
    }

    proxy_pass http://127.0.0.1:2000;
    proxy_http_version 1.1;

    proxy_buffering off;
    proxy_request_buffering off;
    proxy_redirect off;

    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_connect_timeout 3m;
    proxy_send_timeout 3m;
    proxy_read_timeout 3m;

    client_max_body_size 0; # Stream request body to backend
  }
}

server {
  listen 443 ssl http2;
  server_name <base-url>;

  # See https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6see https://ssl-config.mozilla.org/#server=nginx&version=1.18.0&config=intermediate&openssl=1.1.1k&hsts=false&ocsp=false&guideline=5.6
  ssl_session_timeout 1d;
  ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
  ssl_session_tickets off;
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
  ssl_prefer_server_ciphers off;

  location / {
    proxy_pass http://127.0.0.1:2000;
    proxy_http_version 1.1;

    proxy_buffering off;
    proxy_request_buffering off;
    proxy_redirect off;

    proxy_set_header Host $http_host;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_connect_timeout 3m;
    proxy_send_timeout 3m;
    proxy_read_timeout 3m;

    client_max_body_size 0; # Stream request body to backend
  }

    ssl_certificate /etc/letsencrypt/live/<base-url>/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/<base-url>/privkey.pem; # managed by Certbot
}

:computer: Components impacted

:bulb: Screenshots and/or logs

The console logs I see are:

WebSocket connection to 'wss://<base-url>/st_****/ws?auth=*********81cHFqcGhmNXpnZ3pxOTJidXg2' failed: 
Connection.js:80 [Connection, <base-url>/st_****, -224670172] Error occurred: [object Event] Event
Connection.js:74 [Connection, <base-url>/st_****, -224670172] Connection died, retrying in 120 seconds

Screenshot 2023-12-26 at 2 00 39 PM Screenshot 2023-12-26 at 1 19 40 PM

:crystal_ball: Additional context The only additional change I added is authentication. WIthout that, it works perfectly fine. I don't understand exactly why this error message comes up other than 403 error and webSocket connection failed.

geekykant commented 9 months ago

Sorry, false alarm. But still valid for an enhancement as a proper message wasn't displayed in the UI, nor the REST response.

Solution: I missed to setup the Access control part, which sets permission for users to each topic. So go inside the docker container, and run the nfty access commands.

Troubleshooted it from the docker trace logs:

{"level":"TRACE","message":"HTTP request started","http_method":"GET","http_path":"/<topic>/ws?auth=**OTJidXg2","http_request":"GET /<topic>/ws?auth=**OTJidXg2 HTTP/1.1\nPragma: no-cache\nOrigin: https://<base-url>\nAccept-Encoding: gzip, deflate, br\nConnection: upgrade\nX-Forwarded-For: 111.92.127.132\nAccept-Language: en-IN,en-GB;q=0.9,en;q=0.8,en-US;q=0.7,hi;q=0.6\nSec-Websocket-Extensions: permessage-deflate; client_max_window_bits\nSec-Websocket-Key: fpmT/qWG3A1zjYXDGfAGQg==\nUpgrade: websocket\nCache-Control: no-cache\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0\nSec-Websocket-Version: 13","tag":"http","user_id":"u_rYmbLhQnu2","user_name":"geekykant","visitor_auth_limiter_limit":0.016666666666666666,"visitor_auth_limiter_tokens":30,"visitor_id":"ip:111.92.127.132","visitor_ip":"111.92.127.132","visitor_messages":0,"visitor_messages_limit":17280,"visitor_messages_remaining":17280,"visitor_request_limiter_limit":0.2,"visitor_request_limiter_tokens":59.05817581699999,"visitor_seen":"2023-12-26T08:21:51.766Z"}
{"level":"DEBUG","message":"Access to topic <topic> not authorized","error":"unauthorized","http_method":"GET","http_path":"/<topic>/ws?auth=**OTJidXg2","tag":"http","topic":"<topic>","topic_last_access":"2023-12-26T08:21:16.475Z","topic_subscribers":0,"user_id":"u_rYmbLhQnu2","user_name":"geekykant","visitor_auth_limiter_limit":0.016666666666666666,"visitor_auth_limiter_tokens":30,"visitor_id":"ip:111.92.127.132","visitor_ip":"111.92.127.132","visitor_messages":0,"visitor_messages_limit":17280,"visitor_messages_remaining":17280,"visitor_request_limiter_limit":0.2,"visitor_request_limiter_tokens":58.05823781519999,"visitor_seen":"2023-12-26T08:21:51.766Z"}
{"level":"DEBUG","message":"WebSocket error (this error is okay, it happens a lot): forbidden","error":"forbidden","error_code":40301,"http_method":"GET","http_path":"/<topic>/ws?auth=**OTJidXg2","http_status":403,"tag":"websocket","topic":"<topic>","topic_last_access":"2023-12-26T08:21:16.475Z","topic_subscribers":0,"user_id":"u_rYmbLhQnu2","user_name":"geekykant","visitor_auth_limiter_limit":0.016666666666666666,"visitor_auth_limiter_tokens":30,"visitor_id":"ip:111.92.127.132","visitor_ip":"111.92.127.132","visitor_messages":0,"visitor_messages_limit":17280,"visitor_messages_remaining":17280,"visitor_request_limiter_limit":0.2,"visitor_request_limiter_tokens":58.05825770499999,"visitor_seen":"2023-12-26T08:21:51.766Z"}
geekykant commented 9 months ago

@binwiederhier Can we move this to enhancement - to possibly have proper failure messages displayed for access-control and permission related issues?