diff --git a/cmd/serve.go b/cmd/serve.go index 8847f2c..566cce8 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -58,6 +58,10 @@ var flagsServe = append( altsrc.NewDurationFlag(&cli.DurationFlag{Name: "keepalive-interval", Aliases: []string{"keepalive_interval", "k"}, EnvVars: []string{"NTFY_KEEPALIVE_INTERVAL"}, Value: server.DefaultKeepaliveInterval, Usage: "interval of keepalive messages"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "manager-interval", Aliases: []string{"manager_interval", "m"}, EnvVars: []string{"NTFY_MANAGER_INTERVAL"}, Value: server.DefaultManagerInterval, Usage: "interval of for message pruning and stats printing"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "web-root", Aliases: []string{"web_root"}, EnvVars: []string{"NTFY_WEB_ROOT"}, Value: "app", Usage: "sets web root to landing page (home), web app (app) or disabled (disable)"}), + altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-signup", Aliases: []string{"enable_signup"}, EnvVars: []string{"NTFY_ENABLE_SIGNUP"}, Value: false, Usage: "allows users to sign up via the web app, or API"}), + altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-login", Aliases: []string{"enable_login"}, EnvVars: []string{"NTFY_ENABLE_LOGIN"}, Value: false, Usage: "allows users to log in via the web app, or API"}), + altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-reservations", Aliases: []string{"enable_reservations"}, EnvVars: []string{"NTFY_ENABLE_RESERVATIONS"}, Value: false, Usage: "allows users to reserve topics (if their tier allows it)"}), + altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-payments", Aliases: []string{"enable_payments"}, EnvVars: []string{"NTFY_ENABLE_PAYMENTS"}, Value: false, Usage: "enables payments integration [preliminary option, may change]"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "upstream-base-url", Aliases: []string{"upstream_base_url"}, EnvVars: []string{"NTFY_UPSTREAM_BASE_URL"}, Value: "", Usage: "forward poll request to an upstream server, this is needed for iOS push notifications for self-hosted servers"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-addr", Aliases: []string{"smtp_sender_addr"}, EnvVars: []string{"NTFY_SMTP_SENDER_ADDR"}, Usage: "SMTP server address (host:port) for outgoing emails"}), altsrc.NewStringFlag(&cli.StringFlag{Name: "smtp-sender-user", Aliases: []string{"smtp_sender_user"}, EnvVars: []string{"NTFY_SMTP_SENDER_USER"}, Usage: "SMTP user (if e-mail sending is enabled)"}), @@ -76,10 +80,6 @@ var flagsServe = append( altsrc.NewIntFlag(&cli.IntFlag{Name: "visitor-email-limit-burst", Aliases: []string{"visitor_email_limit_burst"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_BURST"}, Value: server.DefaultVisitorEmailLimitBurst, Usage: "initial limit of e-mails per visitor"}), altsrc.NewDurationFlag(&cli.DurationFlag{Name: "visitor-email-limit-replenish", Aliases: []string{"visitor_email_limit_replenish"}, EnvVars: []string{"NTFY_VISITOR_EMAIL_LIMIT_REPLENISH"}, Value: server.DefaultVisitorEmailLimitReplenish, Usage: "interval at which burst limit is replenished (one per x)"}), altsrc.NewBoolFlag(&cli.BoolFlag{Name: "behind-proxy", Aliases: []string{"behind_proxy", "P"}, EnvVars: []string{"NTFY_BEHIND_PROXY"}, Value: false, Usage: "if set, use X-Forwarded-For header to determine visitor IP address (for rate limiting)"}), - altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-signup", Aliases: []string{"enable_signup"}, EnvVars: []string{"NTFY_ENABLE_SIGNUP"}, Value: false, Usage: "xxx"}), - altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-login", Aliases: []string{"enable_login"}, EnvVars: []string{"NTFY_ENABLE_LOGIN"}, Value: false, Usage: "xxx"}), - altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-payments", Aliases: []string{"enable_payments"}, EnvVars: []string{"NTFY_ENABLE_PAYMENTS"}, Value: false, Usage: "xxx"}), - altsrc.NewBoolFlag(&cli.BoolFlag{Name: "enable-reservations", Aliases: []string{"enable_reservations"}, EnvVars: []string{"NTFY_ENABLE_RESERVATIONS"}, Value: false, Usage: "xxx"}), ) var cmdServe = &cli.Command{ @@ -130,6 +130,10 @@ func execServe(c *cli.Context) error { keepaliveInterval := c.Duration("keepalive-interval") managerInterval := c.Duration("manager-interval") webRoot := c.String("web-root") + enableSignup := c.Bool("enable-signup") + enableLogin := c.Bool("enable-login") + enablePayments := c.Bool("enable-payments") + enableReservations := c.Bool("enable-reservations") upstreamBaseURL := c.String("upstream-base-url") smtpSenderAddr := c.String("smtp-sender-addr") smtpSenderUser := c.String("smtp-sender-user") @@ -148,10 +152,6 @@ func execServe(c *cli.Context) error { visitorEmailLimitBurst := c.Int("visitor-email-limit-burst") visitorEmailLimitReplenish := c.Duration("visitor-email-limit-replenish") behindProxy := c.Bool("behind-proxy") - enableSignup := c.Bool("enable-signup") - enableLogin := c.Bool("enable-login") - enablePayments := c.Bool("enable-payments") - enableReservations := c.Bool("enable-reservations") // Check values if firebaseKeyFile != "" && !util.FileExists(firebaseKeyFile) { @@ -190,6 +190,8 @@ func execServe(c *cli.Context) error { return errors.New("base-url and upstream-base-url cannot be identical, you'll likely want to set upstream-base-url to https://ntfy.sh, see https://ntfy.sh/docs/config/#ios-instant-notifications") } else if authFile == "" && (enableSignup || enableLogin || enableReservations || enablePayments) { return errors.New("cannot set enable-signup, enable-login, enable-reserve-topics, or enable-payments if auth-file is not set") + } else if enableSignup && !enableLogin { + return errors.New("cannot set enable-signup without also setting enable-login") } webRootIsApp := webRoot == "app" diff --git a/docs/config.md b/docs/config.md index 79f6780..1fa9ccc 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1034,6 +1034,11 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`). | `visitor-request-limit-exempt-hosts` | `NTFY_VISITOR_REQUEST_LIMIT_EXEMPT_HOSTS` | *comma-separated host/IP list* | - | Rate limiting: List of hostnames and IPs to be exempt from request rate limiting | | `visitor-subscription-limit` | `NTFY_VISITOR_SUBSCRIPTION_LIMIT` | *number* | 30 | Rate limiting: Number of subscriptions per visitor (IP address) | | `web-root` | `NTFY_WEB_ROOT` | `app`, `home` or `disable` | `app` | Sets web root to landing page (home), web app (app) or disables the web app entirely (disable) | +| `enable-signup` | `NTFY_SIGNUP` | *boolean* (`true` or `false`) | `false` | Allows users to sign up via the web app, or API | +| `enable-login` | `NTFY_LOGIN` | *boolean* (`true` or `false`) | `false` | Allows users to log in via the web app, or API | +| `enable-reservations` | `NTFY_RESERVATIONS` | *boolean* (`true` or `false`) | `false` | Allows users to reserve topics (if their tier allows it) | +| `enable-payments` | `NTFY_PAYMENTS` | *boolean* (`true` or `false`) | `false` | Enables payments integration (_preliminary option, may change_) | + The format for a *duration* is: `(smh)`, e.g. 30s, 20m or 1h. The format for a *size* is: `(GMK)`, e.g. 1G, 200M or 4000k. diff --git a/docs/subscribe/api.md b/docs/subscribe/api.md index db1bf3d..b9d72b4 100644 --- a/docs/subscribe/api.md +++ b/docs/subscribe/api.md @@ -319,6 +319,7 @@ format of the message. It's very straight forward: |--------------|----------|---------------------------------------------------|-------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| | `id` | ✔️ | *string* | `hwQ2YpKdmg` | Randomly chosen message identifier | | `time` | ✔️ | *number* | `1635528741` | Message date time, as Unix time stamp | +| `expires` | ✔️ | *number* | `1673542291` | Unix time stamp indicating when the message will be deleted | | `event` | ✔️ | `open`, `keepalive`, `message`, or `poll_request` | `message` | Message type, typically you'd be only interested in `message` | | `topic` | ✔️ | *string* | `topic1,topic2` | Comma-separated list of topics the message is associated with; only one for all `message` events, but may be a list in `open` events | | `message` | - | *string* | `Some message` | Message body; always present in `message` events | @@ -346,6 +347,7 @@ Here's an example for each message type: { "id": "sPs71M8A2T", "time": 1643935928, + "expires": 1643936928, "event": "message", "topic": "mytopic", "priority": 5, @@ -372,6 +374,7 @@ Here's an example for each message type: { "id": "wze9zgqK41", "time": 1638542110, + "expires": 1638543112, "event": "message", "topic": "phil_alerts", "message": "Remote access to phils-laptop detected. Act right away." diff --git a/server/server.go b/server/server.go index 81daa6b..d5ac543 100644 --- a/server/server.go +++ b/server/server.go @@ -47,18 +47,14 @@ import ( - flicker of upgrade banner - JS constants Sync: - - "account topic" sync mechanism - - subscribe to sync topic in UI - "mute" setting - figure out what settings are "web" or "phone" + - sync problems with "deleteAfter=0" and "displayName=" Delete visitor when tier is changed to refresh rate limiters Tests: - Change tier from higher to lower tier (delete reservations) - Message rate limiting and reset tests - test that the visitor is based on the IP address when a user has no tier - Docs: - - "expires" field in message - - server.yml: enable-X flags */ // Server is the main server, providing the UI and API for ntfy diff --git a/server/server.yml b/server/server.yml index 1b26899..3725623 100644 --- a/server/server.yml +++ b/server/server.yml @@ -158,6 +158,19 @@ # # web-root: app +# Various feature flags used to control the web app, and API access, mainly around user and +# account management. +# +# - enable-signup allows users to sign up via the web app, or API +# - enable-login allows users to log in via the web app, or API +# - enable-reservations allows users to reserve topics (if their tier allows it) +# - enable-payments enables payments integration [preliminary option, may change] +# +# enable-signup: false +# enable-login: false +# enable-reservations: false +# enable-payments: false + # Server URL of a Firebase/APNS-connected ntfy server (likely "https://ntfy.sh"). # # iOS users: diff --git a/server/server_account.go b/server/server_account.go index 5234993..23549b2 100644 --- a/server/server_account.go +++ b/server/server_account.go @@ -320,7 +320,7 @@ func (s *Server) handleAccountReservationAdd(w http.ResponseWriter, r *http.Requ if v.user != nil && v.user.Role == user.RoleAdmin { return errHTTPBadRequestMakesNoSenseForAdmin } - req, err := readJSONWithLimit[apiAccountAccessRequest](r.Body, jsonBodyBytesLimit) + req, err := readJSONWithLimit[apiAccountReservationRequest](r.Body, jsonBodyBytesLimit) if err != nil { return err } diff --git a/server/types.go b/server/types.go index 382ff35..9d6bb9e 100644 --- a/server/types.go +++ b/server/types.go @@ -23,10 +23,10 @@ const ( // message represents a message published to a topic type message struct { - ID string `json:"id"` // Random message ID - Time int64 `json:"time"` // Unix time in seconds - Expires int64 `json:"expires"` // Unix time in seconds - Event string `json:"event"` // One of the above + ID string `json:"id"` // Random message ID + Time int64 `json:"time"` // Unix time in seconds + Expires int64 `json:"expires,omitempty"` // Unix time in seconds (not required for open/keepalive) + Event string `json:"event"` // One of the above Topic string `json:"topic"` Title string `json:"title,omitempty"` Message string `json:"message,omitempty"` @@ -281,7 +281,7 @@ type apiAccountResponse struct { Stats *apiAccountStats `json:"stats,omitempty"` } -type apiAccountAccessRequest struct { +type apiAccountReservationRequest struct { Topic string `json:"topic"` Everyone string `json:"everyone"` }