Merge remote-tracking branch 'theatischbein/feat_optional_require_login' into require-login

This commit is contained in:
binwiederhier
2025-08-24 13:34:22 -04:00
10 changed files with 39 additions and 13 deletions

View File

@@ -63,6 +63,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-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-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-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-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: "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"}), 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"}),

View File

@@ -1698,6 +1698,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-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-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) | | `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-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 | | `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 | | `billing-contact` | `NTFY_BILLING_CONTACT` | *email address* or *website* | - | Payments: Email or website displayed in Upgrade dialog as a billing contact |

View File

@@ -256,6 +256,7 @@ func NewConfig() *Config {
EnableSignup: false, EnableSignup: false,
EnableLogin: false, EnableLogin: false,
EnableReservations: false, EnableReservations: false,
RequireLogin: false,
AccessControlAllowOrigin: "*", AccessControlAllowOrigin: "*",
Version: "", Version: "",
WebPushPrivateKey: "", WebPushPrivateKey: "",

View File

@@ -605,6 +605,7 @@ func (s *Server) handleWebConfig(w http.ResponseWriter, _ *http.Request, _ *visi
EnableCalls: s.config.TwilioAccount != "", EnableCalls: s.config.TwilioAccount != "",
EnableEmails: s.config.SMTPSenderFrom != "", EnableEmails: s.config.SMTPSenderFrom != "",
EnableReservations: s.config.EnableReservations, EnableReservations: s.config.EnableReservations,
ReuqireLogin: s.config.RequireLogin,
EnableWebPush: s.config.WebPushPublicKey != "", EnableWebPush: s.config.WebPushPublicKey != "",
BillingContact: s.config.BillingContact, BillingContact: s.config.BillingContact,
WebPushPublicKey: s.config.WebPushPublicKey, WebPushPublicKey: s.config.WebPushPublicKey,

View File

@@ -259,10 +259,12 @@
# - enable-signup allows users to sign up via the web app, or API # - 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-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-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-signup: false
# enable-login: false # enable-login: false
# enable-reservations: false # enable-reservations: false
# require-login: false
# Server URL of a Firebase/APNS-connected ntfy server (likely "https://ntfy.sh"). # Server URL of a Firebase/APNS-connected ntfy server (likely "https://ntfy.sh").
# #

View File

@@ -455,6 +455,7 @@ type apiConfigResponse struct {
EnableEmails bool `json:"enable_emails"` EnableEmails bool `json:"enable_emails"`
EnableReservations bool `json:"enable_reservations"` EnableReservations bool `json:"enable_reservations"`
EnableWebPush bool `json:"enable_web_push"` EnableWebPush bool `json:"enable_web_push"`
RequireLogin bool `json:"require_login"`
BillingContact string `json:"billing_contact"` BillingContact string `json:"billing_contact"`
WebPushPublicKey string `json:"web_push_public_key"` WebPushPublicKey string `json:"web_push_public_key"`
DisallowedTopics []string `json:"disallowed_topics"` DisallowedTopics []string `json:"disallowed_topics"`

View File

@@ -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_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_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_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_example": "Example",
"notifications_more_details": "For more information, check out the <websiteLink>website</websiteLink> or <docsLink>documentation</docsLink>.", "notifications_more_details": "For more information, check out the <websiteLink>website</websiteLink> or <docsLink>documentation</docsLink>.",
"display_name_dialog_title": "Change display name", "display_name_dialog_title": "Change display name",

View File

@@ -135,7 +135,7 @@ const NavList = (props) => {
{showNotificationContextNotSupportedBox && <NotificationContextNotSupportedAlert />} {showNotificationContextNotSupportedBox && <NotificationContextNotSupportedAlert />}
{showNotificationIOSInstallRequired && <NotificationIOSInstallRequiredAlert />} {showNotificationIOSInstallRequired && <NotificationIOSInstallRequiredAlert />}
{alertVisible && <Divider />} {alertVisible && <Divider />}
{!showSubscriptionsList && ( {!showSubscriptionsList && (session.exists() || !config.require_login) && (
<ListItemButton onClick={() => navigate(routes.app)} selected={location.pathname === config.app_root}> <ListItemButton onClick={() => navigate(routes.app)} selected={location.pathname === config.app_root}>
<ListItemIcon> <ListItemIcon>
<ChatBubble /> <ChatBubble />
@@ -164,30 +164,36 @@ const NavList = (props) => {
<ListItemText primary={t("nav_button_account")} /> <ListItemText primary={t("nav_button_account")} />
</ListItemButton> </ListItemButton>
)} )}
{session.exists() || !config.require_login && (
<ListItemButton onClick={() => navigate(routes.settings)} selected={location.pathname === routes.settings}> <ListItemButton onClick={() => navigate(routes.settings)} selected={location.pathname === routes.settings}>
<ListItemIcon> <ListItemIcon>
<SettingsIcon /> <SettingsIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t("nav_button_settings")} /> <ListItemText primary={t("nav_button_settings")} />
</ListItemButton> </ListItemButton>
)}
<ListItemButton onClick={() => openUrl("/docs")}> <ListItemButton onClick={() => openUrl("/docs")}>
<ListItemIcon> <ListItemIcon>
<ArticleIcon /> <ArticleIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t("nav_button_documentation")} /> <ListItemText primary={t("nav_button_documentation")} />
</ListItemButton> </ListItemButton>
{session.exists() || !config.require_login && (
<ListItemButton onClick={() => props.onPublishMessageClick()}> <ListItemButton onClick={() => props.onPublishMessageClick()}>
<ListItemIcon> <ListItemIcon>
<Send /> <Send />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t("nav_button_publish_message")} /> <ListItemText primary={t("nav_button_publish_message")} />
</ListItemButton> </ListItemButton>
)}
{session.exists() || !config.require_login && (
<ListItemButton onClick={() => setSubscribeDialogOpen(true)}> <ListItemButton onClick={() => setSubscribeDialogOpen(true)}>
<ListItemIcon> <ListItemIcon>
<AddIcon /> <AddIcon />
</ListItemIcon> </ListItemIcon>
<ListItemText primary={t("nav_button_subscribe")} /> <ListItemText primary={t("nav_button_subscribe")} />
</ListItemButton> </ListItemButton>
)}
{showUpgradeBanner && ( {showUpgradeBanner && (
// The text background gradient didn't seem to do well with switching between light/dark mode, // 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 // So adding a `key` forces React to replace the entire component when the theme changes

View File

@@ -46,6 +46,7 @@ import priority5 from "../img/priority-5.svg";
import logoOutline from "../img/ntfy-outline.svg"; import logoOutline from "../img/ntfy-outline.svg";
import AttachmentIcon from "./AttachmentIcon"; import AttachmentIcon from "./AttachmentIcon";
import { useAutoSubscribe } from "./hooks"; import { useAutoSubscribe } from "./hooks";
import session from "../app/Session";
const priorityFiles = { const priorityFiles = {
1: priority1, 1: priority1,
@@ -644,12 +645,16 @@ const NoSubscriptions = () => {
<Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}> <Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
<img src={logoOutline} height="64" width="64" alt={t("action_bar_logo_alt")} /> <img src={logoOutline} height="64" width="64" alt={t("action_bar_logo_alt")} />
<br /> <br />
{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")}
</Typography> </Typography>
<Paragraph> <Paragraph>
{t("notifications_no_subscriptions_description", { {!session.exists() && !config.require_login && t("notifications_no_subscriptions_description", {
linktext: t("nav_button_subscribe"), linktext: t("nav_button_subscribe"),
})} })}
{!session.exists() && config.require_login && t("notifications_no_subscriptions_login_description", {
linktext: t("action_bar_sign_in"),
})}
</Paragraph> </Paragraph>
<Paragraph> <Paragraph>
<ForMoreDetails /> <ForMoreDetails />

View File

@@ -65,16 +65,22 @@ const maybeUpdateAccountSettings = async (payload) => {
} }
}; };
const Preferences = () => ( const Preferences = () => {
<Container maxWidth="md" sx={{ marginTop: 3, marginBottom: 3 }}> if (!session.exists() or !config.requireLogin) {
<Stack spacing={3}> window.location.href = routes.app;
<Notifications /> return <></>;
<Reservations /> }
<Users /> return (
<Appearance /> <Container maxWidth="md" sx={{ marginTop: 3, marginBottom: 3 }}>
</Stack> <Stack spacing={3}>
</Container> <Notifications />
); <Reservations />
<Users />
<Appearance />
</Stack>
</Container>
);
};
const Notifications = () => { const Notifications = () => {
const { t } = useTranslation(); const { t } = useTranslation();