diff --git a/cmd/serve.go b/cmd/serve.go
index 62e0a14a..47c9baf4 100644
--- a/cmd/serve.go
+++ b/cmd/serve.go
@@ -62,6 +62,7 @@ var flagsServe = append(
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: "require-login", Aliases: []string{"require_login"}, EnvVars: []string{"NTFY_REQUIRE_LOGIN"}, Value: false, Usage: "all actions via the web app requires a login"}),
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: "upstream-access-token", Aliases: []string{"upstream_access_token"}, EnvVars: []string{"NTFY_UPSTREAM_ACCESS_TOKEN"}, Value: "", Usage: "access token to use for the upstream server; needed only if upstream rate limits are exceeded or upstream server requires auth"}),
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"}),
diff --git a/docs/config.md b/docs/config.md
index 9479301a..6d67e57f 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -1424,6 +1424,7 @@ variable before running the `ntfy` command (e.g. `export NTFY_LISTEN_HTTP=:80`).
| `enable-signup` | `NTFY_ENABLE_SIGNUP` | *boolean* (`true` or `false`) | `false` | Allows users to sign up via the web app, or API |
| `enable-login` | `NTFY_ENABLE_LOGIN` | *boolean* (`true` or `false`) | `false` | Allows users to log in via the web app, or API |
| `enable-reservations` | `NTFY_ENABLE_RESERVATIONS` | *boolean* (`true` or `false`) | `false` | Allows users to reserve topics (if their tier allows it) |
+| `require-login` | `NTFY_REQUIRE_LOGIN` | *boolean* (`true` or `false`) | `false` | All actions via the web app require a login |
| `stripe-secret-key` | `NTFY_STRIPE_SECRET_KEY` | *string* | - | Payments: Key used for the Stripe API communication, this enables payments |
| `stripe-webhook-key` | `NTFY_STRIPE_WEBHOOK_KEY` | *string* | - | Payments: Key required to validate the authenticity of incoming webhooks from Stripe |
| `billing-contact` | `NTFY_BILLING_CONTACT` | *email address* or *website* | - | Payments: Email or website displayed in Upgrade dialog as a billing contact |
diff --git a/server/config.go b/server/config.go
index 7267ce9d..1204910e 100644
--- a/server/config.go
+++ b/server/config.go
@@ -240,6 +240,7 @@ func NewConfig() *Config {
EnableSignup: false,
EnableLogin: false,
EnableReservations: false,
+ RequireLogin: false,
AccessControlAllowOrigin: "*",
Version: "",
WebPushPrivateKey: "",
diff --git a/server/server.go b/server/server.go
index ee2da76a..c922387a 100644
--- a/server/server.go
+++ b/server/server.go
@@ -583,6 +583,7 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
EnableCalls: s.config.TwilioAccount != "",
EnableEmails: s.config.SMTPSenderFrom != "",
EnableReservations: s.config.EnableReservations,
+ ReuqireLogin: s.config.RequireLogin,
EnableWebPush: s.config.WebPushPublicKey != "",
BillingContact: s.config.BillingContact,
WebPushPublicKey: s.config.WebPushPublicKey,
diff --git a/server/server.yml b/server/server.yml
index 7329d37e..ded53c5e 100644
--- a/server/server.yml
+++ b/server/server.yml
@@ -214,10 +214,12 @@
# - 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)
+# - require-login all user actions via the web app require a login
#
# enable-signup: false
# enable-login: false
# enable-reservations: false
+# require-login: false
# Server URL of a Firebase/APNS-connected ntfy server (likely "https://ntfy.sh").
#
diff --git a/server/types.go b/server/types.go
index fb08fb05..2565faa6 100644
--- a/server/types.go
+++ b/server/types.go
@@ -401,6 +401,7 @@ type apiConfigResponse struct {
EnableEmails bool `json:"enable_emails"`
EnableReservations bool `json:"enable_reservations"`
EnableWebPush bool `json:"enable_web_push"`
+ RequireLogin bool `json:"require_login"`
BillingContact string `json:"billing_contact"`
WebPushPublicKey string `json:"web_push_public_key"`
DisallowedTopics []string `json:"disallowed_topics"`
diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json
index 3ad04ea7..e0bc1085 100644
--- a/web/public/static/langs/en.json
+++ b/web/public/static/langs/en.json
@@ -97,6 +97,8 @@
"notifications_none_for_any_description": "To send notifications to a topic, simply PUT or POST to the topic URL. Here's an example using one of your topics.",
"notifications_no_subscriptions_title": "It looks like you don't have any subscriptions yet.",
"notifications_no_subscriptions_description": "Click the \"{{linktext}}\" link to create or subscribe to a topic. After that, you can send messages via PUT or POST and you'll receive notifications here.",
+ "notifications_no_subscriptions_login_title": "This page requires a Login.",
+ "notifications_no_subscriptions_login_description": "Click \"{{linktext}}\" to login into your account.",
"notifications_example": "Example",
"notifications_more_details": "For more information, check out the website or documentation.",
"display_name_dialog_title": "Change display name",
diff --git a/web/src/components/Navigation.jsx b/web/src/components/Navigation.jsx
index 7e30931a..0c4da2e5 100644
--- a/web/src/components/Navigation.jsx
+++ b/web/src/components/Navigation.jsx
@@ -135,7 +135,7 @@ const NavList = (props) => {
{showNotificationContextNotSupportedBox && }
{showNotificationIOSInstallRequired && }
{alertVisible && }
- {!showSubscriptionsList && (
+ {!showSubscriptionsList && (session.exists() || !config.require_login) && (
navigate(routes.app)} selected={location.pathname === config.app_root}>
@@ -164,30 +164,36 @@ const NavList = (props) => {
)}
+ {session.exists() || !config.require_login && (
navigate(routes.settings)} selected={location.pathname === routes.settings}>
+ )}
openUrl("/docs")}>
+ {session.exists() || !config.require_login && (
props.onPublishMessageClick()}>
+ )}
+ {session.exists() || !config.require_login && (
setSubscribeDialogOpen(true)}>
+ )}
{showUpgradeBanner && (
// The text background gradient didn't seem to do well with switching between light/dark mode,
// So adding a `key` forces React to replace the entire component when the theme changes
diff --git a/web/src/components/Notifications.jsx b/web/src/components/Notifications.jsx
index 0b8b2e7d..69b5978e 100644
--- a/web/src/components/Notifications.jsx
+++ b/web/src/components/Notifications.jsx
@@ -37,6 +37,7 @@ import priority5 from "../img/priority-5.svg";
import logoOutline from "../img/ntfy-outline.svg";
import AttachmentIcon from "./AttachmentIcon";
import { useAutoSubscribe } from "./hooks";
+import session from "../app/Session";
const priorityFiles = {
1: priority1,
@@ -634,12 +635,16 @@ const NoSubscriptions = () => {
- {t("notifications_no_subscriptions_title")}
+ {!session.exists() && !config.require_login && t("notifications_no_subscriptions_title")}
+ {!session.exists() && config.require_login && t("notifications_no_subscriptions_login_title")}
- {t("notifications_no_subscriptions_description", {
+ {!session.exists() && !config.require_login && t("notifications_no_subscriptions_description", {
linktext: t("nav_button_subscribe"),
})}
+ {!session.exists() && config.require_login && t("notifications_no_subscriptions_login_description", {
+ linktext: t("action_bar_sign_in"),
+ })}
diff --git a/web/src/components/Preferences.jsx b/web/src/components/Preferences.jsx
index 6770f282..d0767a2a 100644
--- a/web/src/components/Preferences.jsx
+++ b/web/src/components/Preferences.jsx
@@ -65,16 +65,22 @@ const maybeUpdateAccountSettings = async (payload) => {
}
};
-const Preferences = () => (
-
-
-
-
-
-
-
-
-);
+const Preferences = () => {
+ if (!session.exists() or !config.requireLogin) {
+ window.location.href = routes.app;
+ return <>>;
+ }
+ return (
+
+
+
+
+
+
+
+
+ );
+};
const Notifications = () => {
const { t } = useTranslation();