parent
							
								
									4b86085a8c
								
							
						
					
					
						commit
						9684629549
					
				
					 7 changed files with 65 additions and 21 deletions
				
			
		
							
								
								
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
										
									
									
										vendored
									
									
								
							| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
dist/
 | 
					dist/
 | 
				
			||||||
build/
 | 
					build/
 | 
				
			||||||
.idea/
 | 
					.idea/
 | 
				
			||||||
 | 
					*.swp
 | 
				
			||||||
server/docs/
 | 
					server/docs/
 | 
				
			||||||
server/site/
 | 
					server/site/
 | 
				
			||||||
tools/fbsend/fbsend
 | 
					tools/fbsend/fbsend
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,15 +3,16 @@ package cmd
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"github.com/urfave/cli/v2"
 | 
					 | 
				
			||||||
	"github.com/urfave/cli/v2/altsrc"
 | 
					 | 
				
			||||||
	"heckel.io/ntfy/server"
 | 
					 | 
				
			||||||
	"heckel.io/ntfy/util"
 | 
					 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"math"
 | 
						"math"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/urfave/cli/v2"
 | 
				
			||||||
 | 
						"github.com/urfave/cli/v2/altsrc"
 | 
				
			||||||
 | 
						"heckel.io/ntfy/server"
 | 
				
			||||||
 | 
						"heckel.io/ntfy/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
| 
						 | 
					@ -146,8 +147,10 @@ func execServe(c *cli.Context) error {
 | 
				
			||||||
		return errors.New("if set, web-root must be 'home' or 'app'")
 | 
							return errors.New("if set, web-root must be 'home' or 'app'")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Default auth permissions
 | 
					 | 
				
			||||||
	webRootIsApp := webRoot == "app"
 | 
						webRootIsApp := webRoot == "app"
 | 
				
			||||||
 | 
						enableWeb := webRoot != "disable"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Default auth permissions
 | 
				
			||||||
	authDefaultRead := authDefaultAccess == "read-write" || authDefaultAccess == "read-only"
 | 
						authDefaultRead := authDefaultAccess == "read-write" || authDefaultAccess == "read-only"
 | 
				
			||||||
	authDefaultWrite := authDefaultAccess == "read-write" || authDefaultAccess == "write-only"
 | 
						authDefaultWrite := authDefaultAccess == "read-write" || authDefaultAccess == "write-only"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -227,6 +230,7 @@ func execServe(c *cli.Context) error {
 | 
				
			||||||
	conf.VisitorEmailLimitBurst = visitorEmailLimitBurst
 | 
						conf.VisitorEmailLimitBurst = visitorEmailLimitBurst
 | 
				
			||||||
	conf.VisitorEmailLimitReplenish = visitorEmailLimitReplenish
 | 
						conf.VisitorEmailLimitReplenish = visitorEmailLimitReplenish
 | 
				
			||||||
	conf.BehindProxy = behindProxy
 | 
						conf.BehindProxy = behindProxy
 | 
				
			||||||
 | 
						conf.EnableWeb = enableWeb
 | 
				
			||||||
	s, err := server.New(conf)
 | 
						s, err := server.New(conf)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Fatalln(err)
 | 
							log.Fatalln(err)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -802,7 +802,7 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
 | 
				
			||||||
| `smtp-server-addr-prefix`                  | `NTFY_SMTP_SERVER_ADDR_PREFIX`                  | `[ip]:port`                                         | -            | Optional prefix for the e-mail addresses to prevent spam, e.g. `ntfy-`                                                                                                                                                          |
 | 
					| `smtp-server-addr-prefix`                  | `NTFY_SMTP_SERVER_ADDR_PREFIX`                  | `[ip]:port`                                         | -            | Optional prefix for the e-mail addresses to prevent spam, e.g. `ntfy-`                                                                                                                                                          |
 | 
				
			||||||
| `keepalive-interval`                       | `NTFY_KEEPALIVE_INTERVAL`                       | *duration*                                          | 45s          | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. |
 | 
					| `keepalive-interval`                       | `NTFY_KEEPALIVE_INTERVAL`                       | *duration*                                          | 45s          | Interval in which keepalive messages are sent to the client. This is to prevent intermediaries closing the connection for inactivity. Note that the Android app has a hardcoded timeout at 77s, so it should be less than that. |
 | 
				
			||||||
| `manager-interval`                         | `$NTFY_MANAGER_INTERVAL`                        | *duration*                                          | 1m           | Interval in which the manager prunes old messages, deletes topics and prints the stats.                                                                                                                                         |
 | 
					| `manager-interval`                         | `$NTFY_MANAGER_INTERVAL`                        | *duration*                                          | 1m           | Interval in which the manager prunes old messages, deletes topics and prints the stats.                                                                                                                                         |
 | 
				
			||||||
| `web-root`                                 | `NTFY_WEB_ROOT`                                 | `app` or `home`                                     | `app`        | Sets web root to landing page (home) or web app (app)                                                                                                                                                                           |
 | 
					| `web-root`                                 | `NTFY_WEB_ROOT`                                 | `app` or `home`                                     | `app`        | Sets web root to landing page (home), web app (app) or (disable) for no WebUI.                                                                                                                                                  |
 | 
				
			||||||
| `global-topic-limit`                       | `NTFY_GLOBAL_TOPIC_LIMIT`                       | *number*                                            | 15,000       | Rate limiting: Total number of topics before the server rejects new topics.                                                                                                                                                     |
 | 
					| `global-topic-limit`                       | `NTFY_GLOBAL_TOPIC_LIMIT`                       | *number*                                            | 15,000       | Rate limiting: Total number of topics before the server rejects new topics.                                                                                                                                                     |
 | 
				
			||||||
| `visitor-subscription-limit`               | `NTFY_VISITOR_SUBSCRIPTION_LIMIT`               | *number*                                            | 30           | Rate limiting: Number of subscriptions per visitor (IP address)                                                                                                                                                                 |
 | 
					| `visitor-subscription-limit`               | `NTFY_VISITOR_SUBSCRIPTION_LIMIT`               | *number*                                            | 30           | Rate limiting: Number of subscriptions per visitor (IP address)                                                                                                                                                                 |
 | 
				
			||||||
| `visitor-attachment-total-size-limit`      | `NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT`      | *size*                                              | 100M         | Rate limiting: Total storage limit used for attachments per visitor, for all attachments combined. Storage is freed after attachments expire. See `attachment-expiry-duration`.                                                 |
 | 
					| `visitor-attachment-total-size-limit`      | `NTFY_VISITOR_ATTACHMENT_TOTAL_SIZE_LIMIT`      | *size*                                              | 100M         | Rate limiting: Total storage limit used for attachments per visitor, for all attachments combined. Storage is freed after attachments expire. See `attachment-expiry-duration`.                                                 |
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -88,6 +88,7 @@ type Config struct {
 | 
				
			||||||
	VisitorEmailLimitBurst               int
 | 
						VisitorEmailLimitBurst               int
 | 
				
			||||||
	VisitorEmailLimitReplenish           time.Duration
 | 
						VisitorEmailLimitReplenish           time.Duration
 | 
				
			||||||
	BehindProxy                          bool
 | 
						BehindProxy                          bool
 | 
				
			||||||
 | 
						EnableWeb                            bool
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewConfig instantiates a default new server config
 | 
					// NewConfig instantiates a default new server config
 | 
				
			||||||
| 
						 | 
					@ -126,5 +127,6 @@ func NewConfig() *Config {
 | 
				
			||||||
		VisitorEmailLimitBurst:               DefaultVisitorEmailLimitBurst,
 | 
							VisitorEmailLimitBurst:               DefaultVisitorEmailLimitBurst,
 | 
				
			||||||
		VisitorEmailLimitReplenish:           DefaultVisitorEmailLimitReplenish,
 | 
							VisitorEmailLimitReplenish:           DefaultVisitorEmailLimitReplenish,
 | 
				
			||||||
		BehindProxy:                          false,
 | 
							BehindProxy:                          false,
 | 
				
			||||||
 | 
							EnableWeb:                            true,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,11 +8,6 @@ import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"github.com/emersion/go-smtp"
 | 
					 | 
				
			||||||
	"github.com/gorilla/websocket"
 | 
					 | 
				
			||||||
	"golang.org/x/sync/errgroup"
 | 
					 | 
				
			||||||
	"heckel.io/ntfy/auth"
 | 
					 | 
				
			||||||
	"heckel.io/ntfy/util"
 | 
					 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"net"
 | 
						"net"
 | 
				
			||||||
| 
						 | 
					@ -28,6 +23,12 @@ import (
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
	"unicode/utf8"
 | 
						"unicode/utf8"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/emersion/go-smtp"
 | 
				
			||||||
 | 
						"github.com/gorilla/websocket"
 | 
				
			||||||
 | 
						"golang.org/x/sync/errgroup"
 | 
				
			||||||
 | 
						"heckel.io/ntfy/auth"
 | 
				
			||||||
 | 
						"heckel.io/ntfy/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Server is the main server, providing the UI and API for ntfy
 | 
					// Server is the main server, providing the UI and API for ntfy
 | 
				
			||||||
| 
						 | 
					@ -262,19 +263,19 @@ func (s *Server) handle(w http.ResponseWriter, r *http.Request) {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visitor) error {
 | 
					func (s *Server) handleInternal(w http.ResponseWriter, r *http.Request, v *visitor) error {
 | 
				
			||||||
	if r.Method == http.MethodGet && r.URL.Path == "/" {
 | 
						if r.Method == http.MethodGet && r.URL.Path == "/" && s.config.EnableWeb {
 | 
				
			||||||
		return s.handleHome(w, r)
 | 
							return s.handleHome(w, r)
 | 
				
			||||||
	} else if r.Method == http.MethodGet && r.URL.Path == "/example.html" {
 | 
						} else if r.Method == http.MethodGet && r.URL.Path == "/example.html" && s.config.EnableWeb {
 | 
				
			||||||
		return s.handleExample(w, r)
 | 
							return s.handleExample(w, r)
 | 
				
			||||||
	} else if r.Method == http.MethodHead && r.URL.Path == "/" {
 | 
						} else if r.Method == http.MethodHead && r.URL.Path == "/" {
 | 
				
			||||||
		return s.handleEmpty(w, r, v)
 | 
							return s.handleEmpty(w, r, v)
 | 
				
			||||||
	} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath {
 | 
						} else if r.Method == http.MethodGet && r.URL.Path == webConfigPath && s.config.EnableWeb {
 | 
				
			||||||
		return s.handleWebConfig(w, r)
 | 
							return s.handleWebConfig(w, r)
 | 
				
			||||||
	} else if r.Method == http.MethodGet && r.URL.Path == userStatsPath {
 | 
						} else if r.Method == http.MethodGet && r.URL.Path == userStatsPath {
 | 
				
			||||||
		return s.handleUserStats(w, r, v)
 | 
							return s.handleUserStats(w, r, v)
 | 
				
			||||||
	} else if r.Method == http.MethodGet && staticRegex.MatchString(r.URL.Path) {
 | 
						} else if r.Method == http.MethodGet && staticRegex.MatchString(r.URL.Path) && s.config.EnableWeb {
 | 
				
			||||||
		return s.handleStatic(w, r)
 | 
							return s.handleStatic(w, r)
 | 
				
			||||||
	} else if r.Method == http.MethodGet && docsRegex.MatchString(r.URL.Path) {
 | 
						} else if r.Method == http.MethodGet && docsRegex.MatchString(r.URL.Path) && s.config.EnableWeb {
 | 
				
			||||||
		return s.handleDocs(w, r)
 | 
							return s.handleDocs(w, r)
 | 
				
			||||||
	} else if r.Method == http.MethodGet && fileRegex.MatchString(r.URL.Path) && s.config.AttachmentCacheDir != "" {
 | 
						} else if r.Method == http.MethodGet && fileRegex.MatchString(r.URL.Path) && s.config.AttachmentCacheDir != "" {
 | 
				
			||||||
		return s.limitRequests(s.handleFile)(w, r, v)
 | 
							return s.limitRequests(s.handleFile)(w, r, v)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -127,7 +127,8 @@
 | 
				
			||||||
# manager-interval: "1m"
 | 
					# manager-interval: "1m"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Defines if the root route (/) is pointing to the landing page (as on ntfy.sh) or the
 | 
					# Defines if the root route (/) is pointing to the landing page (as on ntfy.sh) or the
 | 
				
			||||||
# web app. If you self-host, you don't want to change this. Can be "app" (default) or "home".
 | 
					# web app. If you self-host, you don't want to change this.
 | 
				
			||||||
 | 
					# Can be "app" (default), "home" or "disable" to disable the WebUI.
 | 
				
			||||||
#
 | 
					#
 | 
				
			||||||
# web-root: app
 | 
					# web-root: app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,9 +6,6 @@ import (
 | 
				
			||||||
	"encoding/base64"
 | 
						"encoding/base64"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"github.com/stretchr/testify/require"
 | 
					 | 
				
			||||||
	"heckel.io/ntfy/auth"
 | 
					 | 
				
			||||||
	"heckel.io/ntfy/util"
 | 
					 | 
				
			||||||
	"math/rand"
 | 
						"math/rand"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/http/httptest"
 | 
						"net/http/httptest"
 | 
				
			||||||
| 
						 | 
					@ -18,6 +15,10 @@ import (
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/require"
 | 
				
			||||||
 | 
						"heckel.io/ntfy/auth"
 | 
				
			||||||
 | 
						"heckel.io/ntfy/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestServer_PublishAndPoll(t *testing.T) {
 | 
					func TestServer_PublishAndPoll(t *testing.T) {
 | 
				
			||||||
| 
						 | 
					@ -162,6 +163,40 @@ func TestServer_StaticSites(t *testing.T) {
 | 
				
			||||||
	require.Contains(t, rr.Body.String(), "</html>")
 | 
						require.Contains(t, rr.Body.String(), "</html>")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestServer_WebEnabled(t *testing.T) {
 | 
				
			||||||
 | 
						conf := newTestConfig(t)
 | 
				
			||||||
 | 
						conf.EnableWeb = false
 | 
				
			||||||
 | 
						s := newTestServer(t, conf)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rr := request(t, s, "GET", "/", "", nil)
 | 
				
			||||||
 | 
						require.Equal(t, 404, rr.Code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rr = request(t, s, "GET", "/example.html", "", nil)
 | 
				
			||||||
 | 
						require.Equal(t, 404, rr.Code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rr = request(t, s, "GET", "/config.js", "", nil)
 | 
				
			||||||
 | 
						require.Equal(t, 404, rr.Code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rr = request(t, s, "GET", "/static/css/home.css", "", nil)
 | 
				
			||||||
 | 
						require.Equal(t, 404, rr.Code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						conf2 := newTestConfig(t)
 | 
				
			||||||
 | 
						conf2.EnableWeb = true
 | 
				
			||||||
 | 
						s2 := newTestServer(t, conf2)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rr = request(t, s2, "GET", "/", "", nil)
 | 
				
			||||||
 | 
						require.Equal(t, 200, rr.Code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rr = request(t, s2, "GET", "/example.html", "", nil)
 | 
				
			||||||
 | 
						require.Equal(t, 200, rr.Code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rr = request(t, s2, "GET", "/config.js", "", nil)
 | 
				
			||||||
 | 
						require.Equal(t, 200, rr.Code)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						rr = request(t, s2, "GET", "/static/css/home.css", "", nil)
 | 
				
			||||||
 | 
						require.Equal(t, 200, rr.Code)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestServer_PublishLargeMessage(t *testing.T) {
 | 
					func TestServer_PublishLargeMessage(t *testing.T) {
 | 
				
			||||||
	c := newTestConfig(t)
 | 
						c := newTestConfig(t)
 | 
				
			||||||
	c.AttachmentCacheDir = "" // Disable attachments
 | 
						c.AttachmentCacheDir = "" // Disable attachments
 | 
				
			||||||
| 
						 | 
					@ -1303,7 +1338,7 @@ func firebaseServiceAccountFile(t *testing.T) string {
 | 
				
			||||||
		return os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT_FILE")
 | 
							return os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT_FILE")
 | 
				
			||||||
	} else if os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT") != "" {
 | 
						} else if os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT") != "" {
 | 
				
			||||||
		filename := filepath.Join(t.TempDir(), "firebase.json")
 | 
							filename := filepath.Join(t.TempDir(), "firebase.json")
 | 
				
			||||||
		require.NotNil(t, os.WriteFile(filename, []byte(os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT")), 0600))
 | 
							require.NotNil(t, os.WriteFile(filename, []byte(os.Getenv("NTFY_TEST_FIREBASE_SERVICE_ACCOUNT")), 0o600))
 | 
				
			||||||
		return filename
 | 
							return filename
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	t.SkipNow()
 | 
						t.SkipNow()
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue