mirror of
https://github.com/multipleof4/ntfy.git
synced 2026-10-16 16:37:55 +00:00
Fix limits for anon users
This commit is contained in:
@@ -624,9 +624,11 @@ func (s *Server) handleAccountEmailVerify(w http.ResponseWriter, r *http.Request
|
|||||||
return errHTTPBadRequestEmailAddressInvalid
|
return errHTTPBadRequestEmailAddressInvalid
|
||||||
}
|
}
|
||||||
// Check user is allowed to add emails
|
// Check user is allowed to add emails
|
||||||
if u == nil || (u.IsUser() && u.Tier == nil) {
|
if u == nil {
|
||||||
return errHTTPUnauthorized
|
return errHTTPUnauthorized
|
||||||
} else if u.IsUser() && u.Tier.EmailLimit == 0 {
|
} else if u.IsUser() && u.Tier != nil && u.Tier.EmailLimit == 0 {
|
||||||
|
return errHTTPUnauthorized
|
||||||
|
} else if u.IsUser() && u.Tier == nil && s.config.VisitorEmailLimitBurst == 0 {
|
||||||
return errHTTPUnauthorized
|
return errHTTPUnauthorized
|
||||||
}
|
}
|
||||||
// Check if email already exists
|
// Check if email already exists
|
||||||
@@ -641,7 +643,7 @@ func (s *Server) handleAccountEmailVerify(w http.ResponseWriter, r *http.Request
|
|||||||
return errHTTPTooManyRequestsLimitEmails
|
return errHTTPTooManyRequestsLimitEmails
|
||||||
}
|
}
|
||||||
// Send verification email
|
// Send verification email
|
||||||
logvr(v, r).Tag(tagAccount).Field("email", req.Email).Debug("Sending email verification")
|
logvr(v, r).Tag(tagAccount).Field("email", req.Email).Info("Sending email verification")
|
||||||
if err := s.mailSender.SendVerification(req.Email); err != nil {
|
if err := s.mailSender.SendVerification(req.Email); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -653,14 +655,12 @@ func (s *Server) handleAccountEmailAdd(w http.ResponseWriter, r *http.Request, v
|
|||||||
req, err := readJSONWithLimit[apiAccountEmailAddRequest](r.Body, jsonBodyBytesLimit, false)
|
req, err := readJSONWithLimit[apiAccountEmailAddRequest](r.Body, jsonBodyBytesLimit, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
} else if !emailAddressRegex.MatchString(req.Email) {
|
||||||
if !emailAddressRegex.MatchString(req.Email) {
|
|
||||||
return errHTTPBadRequestEmailAddressInvalid
|
return errHTTPBadRequestEmailAddressInvalid
|
||||||
}
|
} else if !s.mailSender.CheckVerification(req.Email, req.Code) {
|
||||||
if !s.mailSender.CheckVerification(req.Email, req.Code) {
|
|
||||||
return errHTTPBadRequestEmailVerificationCodeInvalid
|
return errHTTPBadRequestEmailVerificationCodeInvalid
|
||||||
}
|
}
|
||||||
logvr(v, r).Tag(tagAccount).Field("email", req.Email).Debug("Adding email as verified")
|
logvr(v, r).Tag(tagAccount).Field("email", req.Email).Info("Adding email as verified")
|
||||||
if err := s.userManager.AddEmail(u.ID, req.Email); err != nil {
|
if err := s.userManager.AddEmail(u.ID, req.Email); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1692,6 +1692,52 @@ func TestServer_PublishEmailVerify_Disabled_Backwards_Compatible(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServer_AccountEmailVerify_UserWithoutTier(t *testing.T) {
|
||||||
|
// This test verifies that an authenticated user WITHOUT a tier can verify emails
|
||||||
|
// when the default visitor email limit allows it.
|
||||||
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
|
conf := newTestConfigWithAuthFile(t, databaseURL)
|
||||||
|
conf.SMTPSenderVerify = true
|
||||||
|
conf.SMTPSenderAddr = "localhost:25" // Dummy SMTP server (will fail to send, but that's ok)
|
||||||
|
conf.SMTPSenderFrom = "noreply@example.com"
|
||||||
|
s := newTestServer(t, conf)
|
||||||
|
defer s.closeDatabases()
|
||||||
|
|
||||||
|
// Create a user without a tier
|
||||||
|
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
|
||||||
|
|
||||||
|
// Verify email request should NOT return 401
|
||||||
|
response := request(t, s, "PUT", "/v1/account/email/verify", `{"email":"ben@example.com"}`, map[string]string{
|
||||||
|
"Authorization": util.BasicAuth("ben", "ben"),
|
||||||
|
})
|
||||||
|
// The request will fail (SMTP not available), but it must NOT be a 401
|
||||||
|
require.NotEqual(t, 401, response.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestServer_AccountEmailVerify_UserWithoutTier_EmailLimitZero(t *testing.T) {
|
||||||
|
// This test verifies that a tier-less user is rejected when the server's
|
||||||
|
// visitor email limit is zero (email sending disabled).
|
||||||
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
|
conf := newTestConfigWithAuthFile(t, databaseURL)
|
||||||
|
conf.SMTPSenderVerify = true
|
||||||
|
conf.SMTPSenderAddr = "localhost:25"
|
||||||
|
conf.SMTPSenderFrom = "noreply@example.com"
|
||||||
|
conf.VisitorEmailLimitBurst = 0
|
||||||
|
s := newTestServer(t, conf)
|
||||||
|
defer s.closeDatabases()
|
||||||
|
|
||||||
|
// Create a user without a tier
|
||||||
|
require.Nil(t, s.userManager.AddUser("ben", "ben", user.RoleUser, false))
|
||||||
|
|
||||||
|
// Should be rejected with 401 since email sending is disabled
|
||||||
|
response := request(t, s, "PUT", "/v1/account/email/verify", `{"email":"ben@example.com"}`, map[string]string{
|
||||||
|
"Authorization": util.BasicAuth("ben", "ben"),
|
||||||
|
})
|
||||||
|
require.Equal(t, 401, response.Code)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestServer_PublishAndExpungeTopicAfter16Hours(t *testing.T) {
|
func TestServer_PublishAndExpungeTopicAfter16Hours(t *testing.T) {
|
||||||
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
forEachBackend(t, func(t *testing.T, databaseURL string) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|||||||
+5
-1
@@ -440,13 +440,17 @@ func configBasedVisitorLimits(conf *Config) *visitorLimits {
|
|||||||
if conf.VisitorMessageDailyLimit > 0 {
|
if conf.VisitorMessageDailyLimit > 0 {
|
||||||
messagesLimit = int64(conf.VisitorMessageDailyLimit)
|
messagesLimit = int64(conf.VisitorMessageDailyLimit)
|
||||||
}
|
}
|
||||||
|
var emailLimit int64
|
||||||
|
if conf.VisitorEmailLimitBurst > 0 {
|
||||||
|
emailLimit = replenishDurationToDailyLimit(conf.VisitorEmailLimitReplenish) // Approximation!
|
||||||
|
}
|
||||||
return &visitorLimits{
|
return &visitorLimits{
|
||||||
Basis: visitorLimitBasisIP,
|
Basis: visitorLimitBasisIP,
|
||||||
RequestLimitBurst: conf.VisitorRequestLimitBurst,
|
RequestLimitBurst: conf.VisitorRequestLimitBurst,
|
||||||
RequestLimitReplenish: rate.Every(conf.VisitorRequestLimitReplenish),
|
RequestLimitReplenish: rate.Every(conf.VisitorRequestLimitReplenish),
|
||||||
MessageLimit: messagesLimit,
|
MessageLimit: messagesLimit,
|
||||||
MessageExpiryDuration: conf.CacheDuration,
|
MessageExpiryDuration: conf.CacheDuration,
|
||||||
EmailLimit: replenishDurationToDailyLimit(conf.VisitorEmailLimitReplenish), // Approximation!
|
EmailLimit: emailLimit,
|
||||||
EmailLimitBurst: conf.VisitorEmailLimitBurst,
|
EmailLimitBurst: conf.VisitorEmailLimitBurst,
|
||||||
EmailLimitReplenish: rate.Every(conf.VisitorEmailLimitReplenish),
|
EmailLimitReplenish: rate.Every(conf.VisitorEmailLimitReplenish),
|
||||||
CallLimit: visitorDefaultCallsLimit,
|
CallLimit: visitorDefaultCallsLimit,
|
||||||
|
|||||||
@@ -251,6 +251,7 @@
|
|||||||
"account_usage_messages_title": "Published messages",
|
"account_usage_messages_title": "Published messages",
|
||||||
"account_usage_emails_title": "Emails sent",
|
"account_usage_emails_title": "Emails sent",
|
||||||
"account_usage_calls_title": "Phone calls made",
|
"account_usage_calls_title": "Phone calls made",
|
||||||
|
"account_usage_emails_none": "No email notifications can be sent with this account",
|
||||||
"account_usage_calls_none": "No phone calls can be made with this account",
|
"account_usage_calls_none": "No phone calls can be made with this account",
|
||||||
"account_usage_reservations_title": "Reserved topics",
|
"account_usage_reservations_title": "Reserved topics",
|
||||||
"account_usage_reservations_none": "No reserved topics for this account",
|
"account_usage_reservations_none": "No reserved topics for this account",
|
||||||
|
|||||||
@@ -945,7 +945,9 @@ const Stats = () => {
|
|||||||
)}
|
)}
|
||||||
</PrefGroup>
|
</PrefGroup>
|
||||||
{account.role === Role.USER && account.limits.basis === LimitBasis.IP && (
|
{account.role === Role.USER && account.limits.basis === LimitBasis.IP && (
|
||||||
<Typography variant="body1">{t("account_usage_basis_ip_description")}</Typography>
|
<Typography variant="body1" sx={{ pt: 3 }}>
|
||||||
|
{t("account_usage_basis_ip_description")}
|
||||||
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -117,7 +117,8 @@ const NavList = (props) => {
|
|||||||
|
|
||||||
const isAdmin = account?.role === Role.ADMIN;
|
const isAdmin = account?.role === Role.ADMIN;
|
||||||
const isPaid = account?.billing?.subscription;
|
const isPaid = account?.billing?.subscription;
|
||||||
const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid;
|
const hasTier = !!account?.tier;
|
||||||
|
const showUpgradeBanner = config.enable_payments && !isAdmin && !isPaid && !hasTier;
|
||||||
const showSubscriptionsList = props.subscriptions?.length > 0;
|
const showSubscriptionsList = props.subscriptions?.length > 0;
|
||||||
const showNotificationPermissionRequired = useNotificationPermissionListener(() => notifier.notRequested());
|
const showNotificationPermissionRequired = useNotificationPermissionListener(() => notifier.notRequested());
|
||||||
const showNotificationPermissionDenied = useNotificationPermissionListener(() => notifier.denied());
|
const showNotificationPermissionDenied = useNotificationPermissionListener(() => notifier.denied());
|
||||||
|
|||||||
Reference in New Issue
Block a user