From 03aeb707f2a3276239765292d37311786b44e4b4 Mon Sep 17 00:00:00 2001 From: Thea Tischbein Date: Mon, 5 May 2025 11:13:07 +0200 Subject: [PATCH 1/6] feat: Add optional web app flag which requires a login for every action --- cmd/serve.go | 1 + docs/config.md | 1 + server/config.go | 1 + server/server.go | 1 + server/server.yml | 2 ++ server/types.go | 1 + web/public/static/langs/en.json | 2 ++ web/src/components/Navigation.jsx | 8 +++++++- web/src/components/Notifications.jsx | 9 +++++++-- web/src/components/Preferences.jsx | 26 ++++++++++++++++---------- 10 files changed, 39 insertions(+), 13 deletions(-) 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("action_bar_logo_alt")}
- {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(); From 569d89e8f897790a4aa2e5b82f8c9136c7cd1310 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Sun, 24 Aug 2025 13:33:16 -0400 Subject: [PATCH 2/6] Require login --- web/public/config.js | 3 ++- web/src/app/utils.js | 24 ++++++++++++------------ web/src/components/App.jsx | 8 +++++++- web/src/components/Notifications.jsx | 8 +++++++- 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/web/public/config.js b/web/public/config.js index 63bc97bd..2f21dcdd 100644 --- a/web/public/config.js +++ b/web/public/config.js @@ -6,9 +6,10 @@ // During web development, you may change values here for rapid testing. var config = { - base_url: window.location.origin, // Change to test against a different server + base_url: "http://localhost:8080", // window.location.origin, // Change to test against a different server app_root: "/", enable_login: true, + require_login: true, enable_signup: true, enable_payments: false, enable_reservations: true, diff --git a/web/src/app/utils.js b/web/src/app/utils.js index b798589c..c9ad8887 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -79,7 +79,7 @@ export const maybeWithBearerAuth = (headers, token) => { export const withBasicAuth = (headers, username, password) => ({ ...headers, - Authorization: basicAuth(username, password) + Authorization: basicAuth(username, password), }); export const maybeWithAuth = (headers, user) => { @@ -142,7 +142,7 @@ export const getKebabCaseLangStr = (language) => language.replace(/_/g, "-"); export const formatShortDateTime = (timestamp, language) => new Intl.DateTimeFormat(getKebabCaseLangStr(language), { dateStyle: "short", - timeStyle: "short" + timeStyle: "short", }).format(new Date(timestamp * 1000)); export const formatShortDate = (timestamp, language) => @@ -181,32 +181,32 @@ export const openUrl = (url) => { export const sounds = { ding: { file: ding, - label: "Ding" + label: "Ding", }, juntos: { file: juntos, - label: "Juntos" + label: "Juntos", }, pristine: { file: pristine, - label: "Pristine" + label: "Pristine", }, dadum: { file: dadum, - label: "Dadum" + label: "Dadum", }, pop: { file: pop, - label: "Pop" + label: "Pop", }, "pop-swoosh": { file: popSwoosh, - label: "Pop swoosh" + label: "Pop swoosh", }, beep: { file: beep, - label: "Beep" - } + label: "Beep", + }, }; export const playSound = async (id) => { @@ -219,7 +219,7 @@ export const playSound = async (id) => { export async function* fetchLinesIterator(fileURL, headers) { const utf8Decoder = new TextDecoder("utf-8"); const response = await fetch(fileURL, { - headers + headers, }); const reader = response.body.getReader(); let { value: chunk, done: readerDone } = await reader.read(); @@ -228,7 +228,7 @@ export async function* fetchLinesIterator(fileURL, headers) { const re = /\n|\r|\r\n/gm; let startIndex = 0; - for (; ;) { + for (;;) { const result = re.exec(chunk); if (!result) { if (readerDone) { diff --git a/web/src/components/App.jsx b/web/src/components/App.jsx index 7f84b7de..9a2c3e66 100644 --- a/web/src/components/App.jsx +++ b/web/src/components/App.jsx @@ -23,6 +23,7 @@ import Account from "./Account"; import initI18n from "../app/i18n"; // Translations! import prefs, { THEME } from "../app/Prefs"; import RTLCacheProvider from "./RTLCacheProvider"; +import session from "../app/Session"; initI18n(); @@ -45,7 +46,6 @@ const darkModeEnabled = (prefersDarkMode, themePreference) => { const App = () => { const { i18n } = useTranslation(); const languageDir = i18n.dir(); - const [account, setAccount] = useState(null); const accountMemo = useMemo(() => ({ account, setAccount }), [account, setAccount]); const prefersDarkMode = useMediaQuery("(prefers-color-scheme: dark)"); @@ -60,6 +60,12 @@ const App = () => { document.dir = languageDir; }, [i18n.language, languageDir]); + useEffect(() => { + if (!session.exists() && config.require_login && window.location.pathname !== routes.login) { + window.location.href = routes.login; + } + }, []); + return ( }> diff --git a/web/src/components/Notifications.jsx b/web/src/components/Notifications.jsx index 9f984431..449b238b 100644 --- a/web/src/components/Notifications.jsx +++ b/web/src/components/Notifications.jsx @@ -28,7 +28,13 @@ import { useRemark } from "react-remark"; import styled from "@emotion/styled"; import { copyToClipboard, - formatBytes, formatShortDateTime, maybeActionErrors, openUrl, shortUrl, topicShortUrl, unmatchedTags + formatBytes, + formatShortDateTime, + maybeActionErrors, + openUrl, + shortUrl, + topicShortUrl, + unmatchedTags, } from "../app/utils"; import { formatMessage, formatTitle, isImage } from "../app/notificationUtils"; import { LightboxBackdrop, Paragraph, VerticallyCenteredContainer } from "./styles"; From 3de04b27aba3dba8a36f21f1d796adab05050700 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Sun, 24 Aug 2025 13:48:19 -0400 Subject: [PATCH 3/6] Redirect to login page if require-login is enabled --- cmd/serve.go | 2 ++ server/config.go | 1 + server/server.go | 6 +++--- server/types.go | 2 +- web/public/static/langs/en.json | 2 -- web/src/components/Navigation.jsx | 8 +------- web/src/components/Notifications.jsx | 9 ++------- web/src/components/Preferences.jsx | 26 ++++++++++---------------- 8 files changed, 20 insertions(+), 36 deletions(-) diff --git a/cmd/serve.go b/cmd/serve.go index 94349f52..d11778b2 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -172,6 +172,7 @@ func execServe(c *cli.Context) error { webRoot := c.String("web-root") enableSignup := c.Bool("enable-signup") enableLogin := c.Bool("enable-login") + requireLogin := c.Bool("require-login") enableReservations := c.Bool("enable-reservations") upstreamBaseURL := c.String("upstream-base-url") upstreamAccessToken := c.String("upstream-access-token") @@ -476,6 +477,7 @@ func execServe(c *cli.Context) error { conf.BillingContact = billingContact conf.EnableSignup = enableSignup conf.EnableLogin = enableLogin + conf.RequireLogin = requireLogin conf.EnableReservations = enableReservations conf.EnableMetrics = enableMetrics conf.MetricsListenHTTP = metricsListenHTTP diff --git a/server/config.go b/server/config.go index e3de1620..8e7dcda2 100644 --- a/server/config.go +++ b/server/config.go @@ -162,6 +162,7 @@ type Config struct { BillingContact string EnableSignup bool // Enable creation of accounts via API and UI EnableLogin bool + RequireLogin bool EnableReservations bool // Allow users with role "user" to own/reserve topics EnableMetrics bool AccessControlAllowOrigin string // CORS header field to restrict access from web clients diff --git a/server/server.go b/server/server.go index a7dd6d85..fc04d50f 100644 --- a/server/server.go +++ b/server/server.go @@ -9,8 +9,6 @@ import ( "encoding/json" "errors" "fmt" - "gopkg.in/yaml.v2" - "heckel.io/ntfy/v2/payments" "io" "net" "net/http" @@ -33,7 +31,9 @@ import ( "github.com/gorilla/websocket" "github.com/prometheus/client_golang/prometheus/promhttp" "golang.org/x/sync/errgroup" + "gopkg.in/yaml.v2" "heckel.io/ntfy/v2/log" + "heckel.io/ntfy/v2/payments" "heckel.io/ntfy/v2/user" "heckel.io/ntfy/v2/util" "heckel.io/ntfy/v2/util/sprig" @@ -600,12 +600,12 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi BaseURL: "", // Will translate to window.location.origin AppRoot: s.config.WebRoot, EnableLogin: s.config.EnableLogin, + RequireLogin: s.config.RequireLogin, EnableSignup: s.config.EnableSignup, EnablePayments: s.config.StripeSecretKey != "", 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/types.go b/server/types.go index d4aabb40..d9519b94 100644 --- a/server/types.go +++ b/server/types.go @@ -449,13 +449,13 @@ type apiConfigResponse struct { BaseURL string `json:"base_url"` AppRoot string `json:"app_root"` EnableLogin bool `json:"enable_login"` + RequireLogin bool `json:"require_login"` EnableSignup bool `json:"enable_signup"` EnablePayments bool `json:"enable_payments"` EnableCalls bool `json:"enable_calls"` 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 888a9450..b0d3c545 100644 --- a/web/public/static/langs/en.json +++ b/web/public/static/langs/en.json @@ -97,8 +97,6 @@ "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 0c4da2e5..7e30931a 100644 --- a/web/src/components/Navigation.jsx +++ b/web/src/components/Navigation.jsx @@ -135,7 +135,7 @@ const NavList = (props) => { {showNotificationContextNotSupportedBox && } {showNotificationIOSInstallRequired && } {alertVisible && } - {!showSubscriptionsList && (session.exists() || !config.require_login) && ( + {!showSubscriptionsList && ( navigate(routes.app)} selected={location.pathname === config.app_root}> @@ -164,36 +164,30 @@ 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 1995750e..449b238b 100644 --- a/web/src/components/Notifications.jsx +++ b/web/src/components/Notifications.jsx @@ -46,7 +46,6 @@ 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, @@ -645,16 +644,12 @@ const NoSubscriptions = () => { {t("action_bar_logo_alt")}
- {!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_title")}
- {!session.exists() && !config.require_login && t("notifications_no_subscriptions_description", { + {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 dcb90bf9..8621a263 100644 --- a/web/src/components/Preferences.jsx +++ b/web/src/components/Preferences.jsx @@ -65,22 +65,16 @@ const maybeUpdateAccountSettings = async (payload) => { } }; -const Preferences = () => { - if (!session.exists() or !config.requireLogin) { - window.location.href = routes.app; - return <>; - } - return ( - - - - - - - - - ); -}; +const Preferences = () => ( + + + + + + + + +); const Notifications = () => { const { t } = useTranslation(); From 4f6f45a9c06e224a30c44a66df9ce039a7b7fb9e Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Sun, 24 Aug 2025 13:52:04 -0400 Subject: [PATCH 4/6] Checks --- cmd/serve.go | 6 ++++-- web/public/config.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cmd/serve.go b/cmd/serve.go index d11778b2..ab8d75ec 100644 --- a/cmd/serve.go +++ b/cmd/serve.go @@ -320,10 +320,12 @@ func execServe(c *cli.Context) error { return errors.New("if upstream-base-url is set, base-url must also be set") } else if upstreamBaseURL != "" && baseURL != "" && baseURL == upstreamBaseURL { 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 || stripeSecretKey != "") { - return errors.New("cannot set enable-signup, enable-login, enable-reserve-topics, or stripe-secret-key if auth-file is not set") + } else if authFile == "" && (enableSignup || enableLogin || requireLogin || enableReservations || stripeSecretKey != "") { + return errors.New("cannot set enable-signup, enable-login, require-login, enable-reserve-topics, or stripe-secret-key if auth-file is not set") } else if enableSignup && !enableLogin { return errors.New("cannot set enable-signup without also setting enable-login") + } else if requireLogin && !enableLogin { + return errors.New("cannot set require-login without also setting enable-login") } else if !payments.Available && (stripeSecretKey != "" || stripeWebhookKey != "") { return errors.New("cannot set stripe-secret-key or stripe-webhook-key, support for payments is not available in this build (nopayments)") } else if stripeSecretKey != "" && (stripeWebhookKey == "" || baseURL == "") { diff --git a/web/public/config.js b/web/public/config.js index 2f21dcdd..5b904cd5 100644 --- a/web/public/config.js +++ b/web/public/config.js @@ -6,7 +6,7 @@ // During web development, you may change values here for rapid testing. var config = { - base_url: "http://localhost:8080", // window.location.origin, // Change to test against a different server + base_url: window.location.origin, // Change to test against a different server app_root: "/", enable_login: true, require_login: true, From e08f3670d1d5c5bf3d18053e7aad7792bb0e8ccf Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Sun, 24 Aug 2025 13:58:57 -0400 Subject: [PATCH 5/6] Fix lint --- web/src/app/utils.js | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/web/src/app/utils.js b/web/src/app/utils.js index c9ad8887..935f2024 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -277,17 +277,17 @@ export const urlB64ToUint8Array = (base64String) => { export const copyToClipboard = (text) => { if (navigator.clipboard && window.isSecureContext) { return navigator.clipboard.writeText(text); - } else { - const textarea = document.createElement("textarea"); - textarea.value = text; - textarea.setAttribute("readonly", ""); // Avoid mobile keyboards from popping up - textarea.style.position = "fixed"; // Avoid scroll jump - textarea.style.left = "-9999px"; - document.body.appendChild(textarea); - textarea.focus(); - textarea.select(); - document.execCommand("copy"); - document.body.removeChild(textarea); - return Promise.resolve(); } + // Fallback to the older method if clipboard API is not supported (or on HTTP) + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.setAttribute("readonly", ""); // Avoid mobile keyboards from popping up + textarea.style.position = "fixed"; // Avoid scroll jump + textarea.style.left = "-9999px"; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + document.execCommand("copy"); + document.body.removeChild(textarea); + return Promise.resolve(); }; From 50f3563477b7850b438d52eb61bfeec530b77a38 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Sun, 24 Aug 2025 21:18:28 -0400 Subject: [PATCH 6/6] Docs --- docs/releases.md | 4 ++++ server/server.yml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/releases.md b/docs/releases.md index 48c4ec16..15bfa743 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -1470,6 +1470,10 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release ### ntfy server v2.15.0 (UNRELEASED) +**Features:** + +* Add `require-login` flag to redirect to login page if not logged in ([#1434](https://github.com/binwiederhier/ntfy/pull/1434)/[#238](https://github.com/binwiederhier/ntfy/issues/238)/[#1329](https://github.com/binwiederhier/ntfy/pull/1329), thanks to [@theatischbein](https://github.com/theatischbein) for implementing most of this) + **Bug fixes + maintenance:** * Add mutex around message cache writes to avoid `database locked` errors ([#1397](https://github.com/binwiederhier/ntfy/pull/1397), [#1391](https://github.com/binwiederhier/ntfy/issues/1391), thanks to [@timofej673](https://github.com/timofej673)) diff --git a/server/server.yml b/server/server.yml index 648c1671..d9e85453 100644 --- a/server/server.yml +++ b/server/server.yml @@ -258,13 +258,13 @@ # # - 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 +# - require-login redirects users to the login page if they are not logged in (disallows web app access without login) # - 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 +# require-login: false # enable-login: false # enable-reservations: false -# require-login: false # Server URL of a Firebase/APNS-connected ntfy server (likely "https://ntfy.sh"). #