From 81463614c9236bb795b55163c8a6203e6b228f4f Mon Sep 17 00:00:00 2001 From: Hunter Kehoe Date: Sun, 3 Aug 2025 16:07:24 -0600 Subject: [PATCH 1/6] prevent changing a provisioned user's password --- cmd/user_test.go | 8 ++++++++ server/errors.go | 1 + server/server_account.go | 3 +++ server/server_account_test.go | 12 +++++++++++- user/manager.go | 8 ++++++++ user/manager_test.go | 3 +++ user/types.go | 23 ++++++++++++----------- web/src/app/errors.js | 8 ++++++++ 8 files changed, 54 insertions(+), 12 deletions(-) diff --git a/cmd/user_test.go b/cmd/user_test.go index 7a1d5378..ed6f5de4 100644 --- a/cmd/user_test.go +++ b/cmd/user_test.go @@ -60,6 +60,9 @@ func TestCLI_User_Add_Password_Mismatch(t *testing.T) { func TestCLI_User_ChangePass(t *testing.T) { s, conf, port := newTestServerWithAuth(t) + conf.AuthUsers = []*user.User{ + {Name: "philuser", Hash: "$2a$10$U4WSIYY6evyGmZaraavM2e2JeVG6EMGUKN1uUwufUeeRd4Jpg6cGC", Role: user.RoleUser}, // philuser:philpass + } defer test.StopServer(t, s, port) // Add user @@ -73,6 +76,11 @@ func TestCLI_User_ChangePass(t *testing.T) { stdin.WriteString("newpass\nnewpass") require.Nil(t, runUserCommand(app, conf, "change-pass", "phil")) require.Contains(t, stdout.String(), "changed password for user phil") + + // Cannot change provisioned user's pass + app, stdin, _, _ = newTestApp() + stdin.WriteString("newpass\nnewpass") + require.Error(t, runUserCommand(app, conf, "change-pass", "philuser")) } func TestCLI_User_ChangeRole(t *testing.T) { diff --git a/server/errors.go b/server/errors.go index c6745779..1ab12f7c 100644 --- a/server/errors.go +++ b/server/errors.go @@ -132,6 +132,7 @@ var ( errHTTPConflictTopicReserved = &errHTTP{40902, http.StatusConflict, "conflict: access control entry for topic or topic pattern already exists", "", nil} errHTTPConflictSubscriptionExists = &errHTTP{40903, http.StatusConflict, "conflict: topic subscription already exists", "", nil} errHTTPConflictPhoneNumberExists = &errHTTP{40904, http.StatusConflict, "conflict: phone number already exists", "", nil} + errHTTPConflictProvisionedUserPasswordChange = &errHTTP{40905, http.StatusConflict, "conflict: cannot change password of provisioned user", "", nil} errHTTPGonePhoneVerificationExpired = &errHTTP{41001, http.StatusGone, "phone number verification expired or does not exist", "", nil} errHTTPEntityTooLargeAttachment = &errHTTP{41301, http.StatusRequestEntityTooLarge, "attachment too large, or bandwidth limit reached", "https://ntfy.sh/docs/publish/#limitations", nil} errHTTPEntityTooLargeMatrixRequest = &errHTTP{41302, http.StatusRequestEntityTooLarge, "Matrix request is larger than the max allowed length", "", nil} diff --git a/server/server_account.go b/server/server_account.go index f6dcab54..4fe392b1 100644 --- a/server/server_account.go +++ b/server/server_account.go @@ -208,6 +208,9 @@ func (s *Server) handleAccountPasswordChange(w http.ResponseWriter, r *http.Requ } logvr(v, r).Tag(tagAccount).Debug("Changing password for user %s", u.Name) if err := s.userManager.ChangePassword(u.Name, req.NewPassword, false); err != nil { + if errors.Is(err, user.ErrProvisionedUserPasswordChange) { + return errHTTPConflictProvisionedUserPasswordChange + } return err } return s.writeJSON(w, newSuccessResponse()) diff --git a/server/server_account_test.go b/server/server_account_test.go index ba6d0850..409ccfcf 100644 --- a/server/server_account_test.go +++ b/server/server_account_test.go @@ -251,7 +251,11 @@ func TestAccount_Subscription_AddUpdateDelete(t *testing.T) { } func TestAccount_ChangePassword(t *testing.T) { - s := newTestServer(t, newTestConfigWithAuthFile(t)) + conf := newTestConfigWithAuthFile(t) + conf.AuthUsers = []*user.User{ + {Name: "philuser", Hash: "$2a$10$U4WSIYY6evyGmZaraavM2e2JeVG6EMGUKN1uUwufUeeRd4Jpg6cGC", Role: user.RoleUser}, // philuser:philpass + } + s := newTestServer(t, conf) defer s.closeDatabases() require.Nil(t, s.userManager.AddUser("phil", "phil", user.RoleUser, false)) @@ -281,6 +285,12 @@ func TestAccount_ChangePassword(t *testing.T) { "Authorization": util.BasicAuth("phil", "new password"), }) require.Equal(t, 200, rr.Code) + + // Cannot change password of provisioned user + rr = request(t, s, "POST", "/v1/account/password", `{"password": "philpass", "new_password": "new password"}`, map[string]string{ + "Authorization": util.BasicAuth("philuser", "philpass"), + }) + require.Equal(t, 409, rr.Code) } func TestAccount_ChangePassword_NoAccount(t *testing.T) { diff --git a/user/manager.go b/user/manager.go index e24eb542..76abff9d 100644 --- a/user/manager.go +++ b/user/manager.go @@ -1389,6 +1389,14 @@ func (a *Manager) ReservationOwner(topic string) (string, error) { // ChangePassword changes a user's password func (a *Manager) ChangePassword(username, password string, hashed bool) error { + user, err := a.User(username) + if err != nil { + return err + } + if user.Provisioned { + return ErrProvisionedUserPasswordChange + } + return execTx(a.db, func(tx *sql.Tx) error { return a.changePasswordTx(tx, username, password, hashed) }) diff --git a/user/manager_test.go b/user/manager_test.go index d51b9b96..c2ae8b87 100644 --- a/user/manager_test.go +++ b/user/manager_test.go @@ -1209,6 +1209,9 @@ func TestManager_WithProvisionedUsers(t *testing.T) { require.Equal(t, "tk_u48wqendnkx9er21pqqcadlytbutx", tokens[1].Value) require.Equal(t, "Another token", tokens[1].Label) + // Try changing provisioned user's password + require.Error(t, a.ChangePassword("philuser", "new-pass", false)) + // Re-open the DB again (third app start) require.Nil(t, a.db.Close()) conf.Users = []*User{} diff --git a/user/types.go b/user/types.go index 726d40e0..18019bf2 100644 --- a/user/types.go +++ b/user/types.go @@ -244,15 +244,16 @@ const ( // Error constants used by the package var ( - ErrUnauthenticated = errors.New("unauthenticated") - ErrUnauthorized = errors.New("unauthorized") - ErrInvalidArgument = errors.New("invalid argument") - ErrUserNotFound = errors.New("user not found") - ErrUserExists = errors.New("user already exists") - ErrPasswordHashInvalid = errors.New("password hash but be a bcrypt hash, use 'ntfy user hash' to generate") - ErrTierNotFound = errors.New("tier not found") - ErrTokenNotFound = errors.New("token not found") - ErrPhoneNumberNotFound = errors.New("phone number not found") - ErrTooManyReservations = errors.New("new tier has lower reservation limit") - ErrPhoneNumberExists = errors.New("phone number already exists") + ErrUnauthenticated = errors.New("unauthenticated") + ErrUnauthorized = errors.New("unauthorized") + ErrInvalidArgument = errors.New("invalid argument") + ErrUserNotFound = errors.New("user not found") + ErrUserExists = errors.New("user already exists") + ErrPasswordHashInvalid = errors.New("password hash but be a bcrypt hash, use 'ntfy user hash' to generate") + ErrTierNotFound = errors.New("tier not found") + ErrTokenNotFound = errors.New("token not found") + ErrPhoneNumberNotFound = errors.New("phone number not found") + ErrTooManyReservations = errors.New("new tier has lower reservation limit") + ErrPhoneNumberExists = errors.New("phone number already exists") + ErrProvisionedUserPasswordChange = errors.New("cannot change password of provisioned user") ) diff --git a/web/src/app/errors.js b/web/src/app/errors.js index 28f49af1..0f39d705 100644 --- a/web/src/app/errors.js +++ b/web/src/app/errors.js @@ -31,6 +31,14 @@ export class TopicReservedError extends Error { } } +export class ProvisionedUserPasswordError extends Error { + static CODE = 40905; // errHTTPConflictTopicReserved + + constructor() { + super("Cannot change the password of a provisioned user"); + } +} + export class AccountCreateLimitReachedError extends Error { static CODE = 42906; // errHTTPTooManyRequestsLimitAccountCreation From c4c4916bc845ed3ec69c23fb89b61e74e147b3b4 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Mon, 4 Aug 2025 22:22:59 +0200 Subject: [PATCH 2/6] Do not allow changing tokens, user role, or delete users --- server/errors.go | 3 ++- server/server_account.go | 16 +++++++++++-- user/manager.go | 52 +++++++++++++++++++++++++++++----------- user/types.go | 25 +++++++++---------- 4 files changed, 67 insertions(+), 29 deletions(-) diff --git a/server/errors.go b/server/errors.go index 1ab12f7c..098f785d 100644 --- a/server/errors.go +++ b/server/errors.go @@ -132,7 +132,8 @@ var ( errHTTPConflictTopicReserved = &errHTTP{40902, http.StatusConflict, "conflict: access control entry for topic or topic pattern already exists", "", nil} errHTTPConflictSubscriptionExists = &errHTTP{40903, http.StatusConflict, "conflict: topic subscription already exists", "", nil} errHTTPConflictPhoneNumberExists = &errHTTP{40904, http.StatusConflict, "conflict: phone number already exists", "", nil} - errHTTPConflictProvisionedUserPasswordChange = &errHTTP{40905, http.StatusConflict, "conflict: cannot change password of provisioned user", "", nil} + errHTTPConflictProvisionedUserChange = &errHTTP{40905, http.StatusConflict, "conflict: cannot change or delete provisioned user", "", nil} + errHTTPConflictProvisionedTokenChange = &errHTTP{40906, http.StatusConflict, "conflict: cannot change or delete provisioned token", "", nil} errHTTPGonePhoneVerificationExpired = &errHTTP{41001, http.StatusGone, "phone number verification expired or does not exist", "", nil} errHTTPEntityTooLargeAttachment = &errHTTP{41301, http.StatusRequestEntityTooLarge, "attachment too large, or bandwidth limit reached", "https://ntfy.sh/docs/publish/#limitations", nil} errHTTPEntityTooLargeMatrixRequest = &errHTTP{41302, http.StatusRequestEntityTooLarge, "Matrix request is larger than the max allowed length", "", nil} diff --git a/server/server_account.go b/server/server_account.go index 4fe392b1..de5a41d5 100644 --- a/server/server_account.go +++ b/server/server_account.go @@ -174,6 +174,12 @@ func (s *Server) handleAccountDelete(w http.ResponseWriter, r *http.Request, v * if _, err := s.userManager.Authenticate(u.Name, req.Password); err != nil { return errHTTPBadRequestIncorrectPasswordConfirmation } + if err := s.userManager.CanChangeUser(u.Name); err != nil { + if errors.Is(err, user.ErrProvisionedUserChange) { + return errHTTPConflictProvisionedUserChange + } + return err + } if s.webPush != nil && u.ID != "" { if err := s.webPush.RemoveSubscriptionsByUserID(u.ID); err != nil { logvr(v, r).Err(err).Warn("Error removing web push subscriptions for %s", u.Name) @@ -208,8 +214,8 @@ func (s *Server) handleAccountPasswordChange(w http.ResponseWriter, r *http.Requ } logvr(v, r).Tag(tagAccount).Debug("Changing password for user %s", u.Name) if err := s.userManager.ChangePassword(u.Name, req.NewPassword, false); err != nil { - if errors.Is(err, user.ErrProvisionedUserPasswordChange) { - return errHTTPConflictProvisionedUserPasswordChange + if errors.Is(err, user.ErrProvisionedUserChange) { + return errHTTPConflictProvisionedUserChange } return err } @@ -277,6 +283,9 @@ func (s *Server) handleAccountTokenUpdate(w http.ResponseWriter, r *http.Request Debug("Updating token for user %s as deleted", u.Name) token, err := s.userManager.ChangeToken(u.ID, req.Token, req.Label, expires) if err != nil { + if errors.Is(err, user.ErrProvisionedTokenChange) { + return errHTTPConflictProvisionedTokenChange + } return err } response := &apiAccountTokenResponse{ @@ -299,6 +308,9 @@ func (s *Server) handleAccountTokenDelete(w http.ResponseWriter, r *http.Request } } if err := s.userManager.RemoveToken(u.ID, token); err != nil { + if errors.Is(err, user.ErrProvisionedTokenChange) { + return errHTTPConflictProvisionedTokenChange + } return err } logvr(v, r). diff --git a/user/manager.go b/user/manager.go index 76abff9d..8cea653c 100644 --- a/user/manager.go +++ b/user/manager.go @@ -773,6 +773,9 @@ func (a *Manager) ChangeToken(userID, token string, label *string, expires *time if token == "" { return nil, errNoTokenProvided } + if err := a.CanChangeToken(userID, token); err != nil { + return nil, err + } tx, err := a.db.Begin() if err != nil { return nil, err @@ -796,6 +799,9 @@ func (a *Manager) ChangeToken(userID, token string, label *string, expires *time // RemoveToken deletes the token defined in User.Token func (a *Manager) RemoveToken(userID, token string) error { + if err := a.CanChangeToken(userID, token); err != nil { + return err + } return execTx(a.db, func(tx *sql.Tx) error { return a.removeTokenTx(tx, userID, token) }) @@ -811,6 +817,17 @@ func (a *Manager) removeTokenTx(tx *sql.Tx, userID, token string) error { return nil } +// CanChangeToken checks if the token can be changed. If the token is provisioned, it cannot be changed. +func (a *Manager) CanChangeToken(userID, token string) error { + t, err := a.Token(userID, token) + if err != nil { + return err + } else if t.Provisioned { + return ErrProvisionedTokenChange + } + return nil +} + // RemoveExpiredTokens deletes all expired tokens from the database func (a *Manager) RemoveExpiredTokens() error { if _, err := a.db.Exec(deleteExpiredTokensQuery, time.Now().Unix()); err != nil { @@ -1072,6 +1089,9 @@ func (a *Manager) addUserTx(tx *sql.Tx, username, password string, role Role, ha // RemoveUser deletes the user with the given username. The function returns nil on success, even // if the user did not exist in the first place. func (a *Manager) RemoveUser(username string) error { + if err := a.CanChangeUser(username); err != nil { + return err + } return execTx(a.db, func(tx *sql.Tx) error { return a.removeUserTx(tx, username) }) @@ -1389,19 +1409,26 @@ func (a *Manager) ReservationOwner(topic string) (string, error) { // ChangePassword changes a user's password func (a *Manager) ChangePassword(username, password string, hashed bool) error { - user, err := a.User(username) - if err != nil { + if err := a.CanChangeUser(username); err != nil { return err } - if user.Provisioned { - return ErrProvisionedUserPasswordChange - } - return execTx(a.db, func(tx *sql.Tx) error { return a.changePasswordTx(tx, username, password, hashed) }) } +// CanChangeUser checks if the user with the given username can be changed. +// This is used to prevent changes to provisioned users, which are defined in the config file. +func (a *Manager) CanChangeUser(username string) error { + user, err := a.User(username) + if err != nil { + return err + } else if user.Provisioned { + return ErrProvisionedUserChange + } + return nil +} + func (a *Manager) changePasswordTx(tx *sql.Tx, username, password string, hashed bool) error { var hash string var err error @@ -1425,6 +1452,9 @@ func (a *Manager) changePasswordTx(tx *sql.Tx, username, password string, hashed // ChangeRole changes a user's role. When a role is changed from RoleUser to RoleAdmin, // all existing access control entries (Grant) are removed, since they are no longer needed. func (a *Manager) ChangeRole(username string, role Role) error { + if err := a.CanChangeUser(username); err != nil { + return err + } return execTx(a.db, func(tx *sql.Tx) error { return a.changeRoleTx(tx, username, role) }) @@ -1445,14 +1475,8 @@ func (a *Manager) changeRoleTx(tx *sql.Tx, username string, role Role) error { return nil } -// ChangeProvisioned changes the provisioned status of a user. This is used to mark users as +// changeProvisionedTx changes the provisioned status of a user. This is used to mark users as // provisioned. A provisioned user is a user defined in the config file. -func (a *Manager) ChangeProvisioned(username string, provisioned bool) error { - return execTx(a.db, func(tx *sql.Tx) error { - return a.changeProvisionedTx(tx, username, provisioned) - }) -} - func (a *Manager) changeProvisionedTx(tx *sql.Tx, username string, provisioned bool) error { if _, err := tx.Exec(updateUserProvisionedQuery, provisioned, username); err != nil { return err @@ -1678,7 +1702,7 @@ func (a *Manager) Tiers() ([]*Tier, error) { tiers := make([]*Tier, 0) for { tier, err := a.readTier(rows) - if err == ErrTierNotFound { + if errors.Is(err, ErrTierNotFound) { break } else if err != nil { return nil, err diff --git a/user/types.go b/user/types.go index 18019bf2..e501e732 100644 --- a/user/types.go +++ b/user/types.go @@ -244,16 +244,17 @@ const ( // Error constants used by the package var ( - ErrUnauthenticated = errors.New("unauthenticated") - ErrUnauthorized = errors.New("unauthorized") - ErrInvalidArgument = errors.New("invalid argument") - ErrUserNotFound = errors.New("user not found") - ErrUserExists = errors.New("user already exists") - ErrPasswordHashInvalid = errors.New("password hash but be a bcrypt hash, use 'ntfy user hash' to generate") - ErrTierNotFound = errors.New("tier not found") - ErrTokenNotFound = errors.New("token not found") - ErrPhoneNumberNotFound = errors.New("phone number not found") - ErrTooManyReservations = errors.New("new tier has lower reservation limit") - ErrPhoneNumberExists = errors.New("phone number already exists") - ErrProvisionedUserPasswordChange = errors.New("cannot change password of provisioned user") + ErrUnauthenticated = errors.New("unauthenticated") + ErrUnauthorized = errors.New("unauthorized") + ErrInvalidArgument = errors.New("invalid argument") + ErrUserNotFound = errors.New("user not found") + ErrUserExists = errors.New("user already exists") + ErrPasswordHashInvalid = errors.New("password hash but be a bcrypt hash, use 'ntfy user hash' to generate") + ErrTierNotFound = errors.New("tier not found") + ErrTokenNotFound = errors.New("token not found") + ErrPhoneNumberNotFound = errors.New("phone number not found") + ErrTooManyReservations = errors.New("new tier has lower reservation limit") + ErrPhoneNumberExists = errors.New("phone number already exists") + ErrProvisionedUserChange = errors.New("cannot change or delete provisioned user") + ErrProvisionedTokenChange = errors.New("cannot change or delete provisioned token") ) From bcfb50b35ae4cfee8f07b7aa72c009cecc3cf94b Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Tue, 5 Aug 2025 09:59:23 +0200 Subject: [PATCH 3/6] Disallow changing provisioned user and tokens --- server/server_account.go | 12 ++++---- server/types.go | 12 ++++---- web/public/static/langs/en.json | 2 ++ web/src/app/errors.js | 8 ----- web/src/components/Account.jsx | 52 ++++++++++++++++++++++++++------- 5 files changed, 58 insertions(+), 28 deletions(-) diff --git a/server/server_account.go b/server/server_account.go index de5a41d5..6efb2f43 100644 --- a/server/server_account.go +++ b/server/server_account.go @@ -85,6 +85,7 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis response.Username = u.Name response.Role = string(u.Role) response.SyncTopic = u.SyncTopic + response.Provisioned = u.Provisioned if u.Prefs != nil { if u.Prefs.Language != nil { response.Language = *u.Prefs.Language @@ -139,11 +140,12 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis lastOrigin = t.LastOrigin.String() } response.Tokens = append(response.Tokens, &apiAccountTokenResponse{ - Token: t.Value, - Label: t.Label, - LastAccess: t.LastAccess.Unix(), - LastOrigin: lastOrigin, - Expires: t.Expires.Unix(), + Token: t.Value, + Label: t.Label, + LastAccess: t.LastAccess.Unix(), + LastOrigin: lastOrigin, + Expires: t.Expires.Unix(), + Provisioned: t.Provisioned, }) } } diff --git a/server/types.go b/server/types.go index 65492e46..b8a82883 100644 --- a/server/types.go +++ b/server/types.go @@ -360,11 +360,12 @@ type apiAccountTokenUpdateRequest struct { } type apiAccountTokenResponse struct { - Token string `json:"token"` - Label string `json:"label,omitempty"` - LastAccess int64 `json:"last_access,omitempty"` - LastOrigin string `json:"last_origin,omitempty"` - Expires int64 `json:"expires,omitempty"` // Unix timestamp + Token string `json:"token"` + Label string `json:"label,omitempty"` + LastAccess int64 `json:"last_access,omitempty"` + LastOrigin string `json:"last_origin,omitempty"` + Expires int64 `json:"expires,omitempty"` // Unix timestamp + Provisioned bool `json:"provisioned,omitempty"` // True if this token was provisioned by the server config } type apiAccountPhoneNumberVerifyRequest struct { @@ -426,6 +427,7 @@ type apiAccountResponse struct { Username string `json:"username"` Role string `json:"role,omitempty"` SyncTopic string `json:"sync_topic,omitempty"` + Provisioned bool `json:"provisioned,omitempty"` Language string `json:"language,omitempty"` Notification *user.NotificationPrefs `json:"notification,omitempty"` Subscriptions []*user.Subscription `json:"subscriptions,omitempty"` diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json index 3ad04ea7..1dbf763d 100644 --- a/web/public/static/langs/en.json +++ b/web/public/static/langs/en.json @@ -212,6 +212,7 @@ "account_basics_phone_numbers_dialog_check_verification_button": "Confirm code", "account_basics_phone_numbers_dialog_channel_sms": "SMS", "account_basics_phone_numbers_dialog_channel_call": "Call", + "account_basics_cannot_edit_or_delete_provisioned_user": "A provisioned user cannot be edited or deleted from the web app", "account_usage_title": "Usage", "account_usage_of_limit": "of {{limit}}", "account_usage_unlimited": "Unlimited", @@ -291,6 +292,7 @@ "account_tokens_table_current_session": "Current browser session", "account_tokens_table_copied_to_clipboard": "Access token copied", "account_tokens_table_cannot_delete_or_edit": "Cannot edit or delete current session token", + "account_tokens_table_cannot_delete_or_edit_provisioned_token": "Cannot edit or delete provisioned token", "account_tokens_table_create_token_button": "Create access token", "account_tokens_table_last_origin_tooltip": "From IP address {{ip}}, click to lookup", "account_tokens_dialog_title_create": "Create access token", diff --git a/web/src/app/errors.js b/web/src/app/errors.js index 0f39d705..28f49af1 100644 --- a/web/src/app/errors.js +++ b/web/src/app/errors.js @@ -31,14 +31,6 @@ export class TopicReservedError extends Error { } } -export class ProvisionedUserPasswordError extends Error { - static CODE = 40905; // errHTTPConflictTopicReserved - - constructor() { - super("Cannot change the password of a provisioned user"); - } -} - export class AccountCreateLimitReachedError extends Error { static CODE = 42906; // errHTTPTooManyRequestsLimitAccountCreation diff --git a/web/src/components/Account.jsx b/web/src/components/Account.jsx index 319353df..65aa38e8 100644 --- a/web/src/components/Account.jsx +++ b/web/src/components/Account.jsx @@ -100,15 +100,13 @@ const Username = () => {
{session.username()} - {account?.role === Role.ADMIN ? ( + {account?.role === Role.ADMIN && ( <> {" "} 👑 - ) : ( - "" )}
@@ -119,6 +117,7 @@ const ChangePassword = () => { const { t } = useTranslation(); const [dialogKey, setDialogKey] = useState(0); const [dialogOpen, setDialogOpen] = useState(false); + const { account } = useContext(AccountContext); const labelId = "prefChangePassword"; const handleDialogOpen = () => { @@ -136,9 +135,19 @@ const ChangePassword = () => { ⬤⬤⬤⬤⬤⬤⬤⬤⬤⬤ - - - + {!account?.provisioned ? ( + + + + ) : ( + + + + + + + + )} @@ -888,7 +897,7 @@ const TokensTable = (props) => { - {token.token !== session.token() && ( + {token.token !== session.token() && !token.provisioned && ( <> handleEditClick(token)} aria-label={t("account_tokens_dialog_title_edit")}> @@ -910,6 +919,18 @@ const TokensTable = (props) => { )} + {token.provisioned && ( + + + + + + + + + + + )} ))} @@ -1048,6 +1069,7 @@ const DeleteAccount = () => { const { t } = useTranslation(); const [dialogKey, setDialogKey] = useState(0); const [dialogOpen, setDialogOpen] = useState(false); + const { account } = useContext(AccountContext); const handleDialogOpen = () => { setDialogKey((prev) => prev + 1); @@ -1061,9 +1083,19 @@ const DeleteAccount = () => { return (
- + {!account?.provisioned ? ( + + ) : ( + + + + + + )}
From cef228f880f3762a623661e83640221612f7934b Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Tue, 5 Aug 2025 10:01:21 +0200 Subject: [PATCH 4/6] Derp --- web/public/static/langs/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json index 1dbf763d..b0d3c545 100644 --- a/web/public/static/langs/en.json +++ b/web/public/static/langs/en.json @@ -212,7 +212,7 @@ "account_basics_phone_numbers_dialog_check_verification_button": "Confirm code", "account_basics_phone_numbers_dialog_channel_sms": "SMS", "account_basics_phone_numbers_dialog_channel_call": "Call", - "account_basics_cannot_edit_or_delete_provisioned_user": "A provisioned user cannot be edited or deleted from the web app", + "account_basics_cannot_edit_or_delete_provisioned_user": "A provisioned user cannot be edited or deleted", "account_usage_title": "Usage", "account_usage_of_limit": "of {{limit}}", "account_usage_unlimited": "Unlimited", From d35dfc14d12cf42014806500bb8e721b63194679 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Tue, 5 Aug 2025 10:09:58 +0200 Subject: [PATCH 5/6] Bump release notes and such --- docs/install.md | 60 +++++++++++++++++++++---------------------- docs/releases.md | 24 +++++++++++------ go.mod | 12 ++++----- go.sum | 24 ++++++++--------- web/package-lock.json | 31 +++++++++++----------- 5 files changed, 79 insertions(+), 72 deletions(-) diff --git a/docs/install.md b/docs/install.md index b841e950..3ad34508 100644 --- a/docs/install.md +++ b/docs/install.md @@ -30,37 +30,37 @@ deb/rpm packages. === "x86_64/amd64" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_amd64.tar.gz - tar zxvf ntfy_2.13.0_linux_amd64.tar.gz - sudo cp -a ntfy_2.13.0_linux_amd64/ntfy /usr/local/bin/ntfy - sudo mkdir /etc/ntfy && sudo cp ntfy_2.13.0_linux_amd64/{client,server}/*.yml /etc/ntfy + wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_amd64.tar.gz + tar zxvf ntfy_2.14.0_linux_amd64.tar.gz + sudo cp -a ntfy_2.14.0_linux_amd64/ntfy /usr/local/bin/ntfy + sudo mkdir /etc/ntfy && sudo cp ntfy_2.14.0_linux_amd64/{client,server}/*.yml /etc/ntfy sudo ntfy serve ``` === "armv6" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv6.tar.gz - tar zxvf ntfy_2.13.0_linux_armv6.tar.gz - sudo cp -a ntfy_2.13.0_linux_armv6/ntfy /usr/bin/ntfy - sudo mkdir /etc/ntfy && sudo cp ntfy_2.13.0_linux_armv6/{client,server}/*.yml /etc/ntfy + wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv6.tar.gz + tar zxvf ntfy_2.14.0_linux_armv6.tar.gz + sudo cp -a ntfy_2.14.0_linux_armv6/ntfy /usr/bin/ntfy + sudo mkdir /etc/ntfy && sudo cp ntfy_2.14.0_linux_armv6/{client,server}/*.yml /etc/ntfy sudo ntfy serve ``` === "armv7/armhf" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv7.tar.gz - tar zxvf ntfy_2.13.0_linux_armv7.tar.gz - sudo cp -a ntfy_2.13.0_linux_armv7/ntfy /usr/bin/ntfy - sudo mkdir /etc/ntfy && sudo cp ntfy_2.13.0_linux_armv7/{client,server}/*.yml /etc/ntfy + wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv7.tar.gz + tar zxvf ntfy_2.14.0_linux_armv7.tar.gz + sudo cp -a ntfy_2.14.0_linux_armv7/ntfy /usr/bin/ntfy + sudo mkdir /etc/ntfy && sudo cp ntfy_2.14.0_linux_armv7/{client,server}/*.yml /etc/ntfy sudo ntfy serve ``` === "arm64" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_arm64.tar.gz - tar zxvf ntfy_2.13.0_linux_arm64.tar.gz - sudo cp -a ntfy_2.13.0_linux_arm64/ntfy /usr/bin/ntfy - sudo mkdir /etc/ntfy && sudo cp ntfy_2.13.0_linux_arm64/{client,server}/*.yml /etc/ntfy + wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_arm64.tar.gz + tar zxvf ntfy_2.14.0_linux_arm64.tar.gz + sudo cp -a ntfy_2.14.0_linux_arm64/ntfy /usr/bin/ntfy + sudo mkdir /etc/ntfy && sudo cp ntfy_2.14.0_linux_arm64/{client,server}/*.yml /etc/ntfy sudo ntfy serve ``` @@ -110,7 +110,7 @@ Manually installing the .deb file: === "x86_64/amd64" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_amd64.deb + wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_amd64.deb sudo dpkg -i ntfy_*.deb sudo systemctl enable ntfy sudo systemctl start ntfy @@ -118,7 +118,7 @@ Manually installing the .deb file: === "armv6" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv6.deb + wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv6.deb sudo dpkg -i ntfy_*.deb sudo systemctl enable ntfy sudo systemctl start ntfy @@ -126,7 +126,7 @@ Manually installing the .deb file: === "armv7/armhf" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv7.deb + wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv7.deb sudo dpkg -i ntfy_*.deb sudo systemctl enable ntfy sudo systemctl start ntfy @@ -134,7 +134,7 @@ Manually installing the .deb file: === "arm64" ```bash - wget https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_arm64.deb + wget https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_arm64.deb sudo dpkg -i ntfy_*.deb sudo systemctl enable ntfy sudo systemctl start ntfy @@ -144,28 +144,28 @@ Manually installing the .deb file: === "x86_64/amd64" ```bash - sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_amd64.rpm + sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_amd64.rpm sudo systemctl enable ntfy sudo systemctl start ntfy ``` === "armv6" ```bash - sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv6.rpm + sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv6.rpm sudo systemctl enable ntfy sudo systemctl start ntfy ``` === "armv7/armhf" ```bash - sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_armv7.rpm + sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_armv7.rpm sudo systemctl enable ntfy sudo systemctl start ntfy ``` === "arm64" ```bash - sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_linux_arm64.rpm + sudo rpm -ivh https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_linux_arm64.rpm sudo systemctl enable ntfy sudo systemctl start ntfy ``` @@ -195,18 +195,18 @@ NixOS also supports [declarative setup of the ntfy server](https://search.nixos. ## macOS The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on macOS as well. -To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_darwin_all.tar.gz), +To install, please [download the tarball](https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_darwin_all.tar.gz), extract it and place it somewhere in your `PATH` (e.g. `/usr/local/bin/ntfy`). If run as `root`, ntfy will look for its config at `/etc/ntfy/client.yml`. For all other users, it'll look for it at `~/Library/Application Support/ntfy/client.yml` (sample included in the tarball). ```bash -curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_darwin_all.tar.gz > ntfy_2.13.0_darwin_all.tar.gz -tar zxvf ntfy_2.13.0_darwin_all.tar.gz -sudo cp -a ntfy_2.13.0_darwin_all/ntfy /usr/local/bin/ntfy +curl -L https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_darwin_all.tar.gz > ntfy_2.14.0_darwin_all.tar.gz +tar zxvf ntfy_2.14.0_darwin_all.tar.gz +sudo cp -a ntfy_2.14.0_darwin_all/ntfy /usr/local/bin/ntfy mkdir ~/Library/Application\ Support/ntfy -cp ntfy_2.13.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml +cp ntfy_2.14.0_darwin_all/client/client.yml ~/Library/Application\ Support/ntfy/client.yml ntfy --help ``` @@ -224,7 +224,7 @@ brew install ntfy ## Windows The [ntfy CLI](subscribe/cli.md) (`ntfy publish` and `ntfy subscribe` only) is supported on Windows as well. -To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.13.0/ntfy_2.13.0_windows_amd64.zip), +To install, please [download the latest ZIP](https://github.com/binwiederhier/ntfy/releases/download/v2.14.0/ntfy_2.14.0_windows_amd64.zip), extract it and place the `ntfy.exe` binary somewhere in your `%Path%`. The default path for the client config file is at `%AppData%\ntfy\client.yml` (not created automatically, sample in the ZIP file). diff --git a/docs/releases.md b/docs/releases.md index 7e383dce..a78c6e7c 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -2,6 +2,22 @@ Binaries for all releases can be found on the GitHub releases pages for the [ntfy server](https://github.com/binwiederhier/ntfy/releases) and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/releases). +### ntfy server v2.14.0 +Released August 5, 2025 + +This release adds support for [declarative users](config.md#users-via-the-config), [declarative ACL entries](config.md#acl-entries-via-the-config) and [declarative tokens](config.md#tokens-via-the-config). This allows you to define users, ACL entries and tokens in the config file, which is useful for static deployments or deployments that use a configuration management system. + +It also adds support for [pre-defined templates](publish.md#pre-defined-templates) and [custom templates](publish.md#custom-templates) for enhanced JSON webhook support, as well as advanced [template functions](publish.md#template-functions) based on the [Sprig](https://github.com/Masterminds/sprig) functions. + +❤️ If you like ntfy, **please consider sponsoring me** via [GitHub Sponsors](https://github.com/sponsors/binwiederhier), [Liberapay](https://en.liberapay.com/ntfy/), Bitcoin (`1626wjrw3uWk9adyjCfYwafw4sQWujyjn8`), or by buying a [paid plan via the web app](https://ntfy.sh/app). ntfy +will always remain open source. + +**Features:** + +* [Declarative users](config.md#users-via-the-config), [declarative ACL entries](config.md#acl-entries-via-the-config) and [declarative tokens](config.md#tokens-via-the-config) ([#464](https://github.com/binwiederhier/ntfy/issues/464), [#1384](https://github.com/binwiederhier/ntfy/pull/1384), thanks to [pinpox](https://github.com/pinpox) for reporting, to [@wunter8](https://github.com/wunter8) for reviewing) +* [Pre-defined templates](publish.md#pre-defined-templates) and [custom templates](publish.md#custom-templates) for enhanced JSON webhook support ([#1390](https://github.com/binwiederhier/ntfy/pull/1390)) +* Support of advanced [template functions](publish.md#template-functions) based on the [Sprig](https://github.com/Masterminds/sprig) library ([#1121](https://github.com/binwiederhier/ntfy/issues/1121), thanks to [@davidatkinsondoyle](https://github.com/davidatkinsondoyle) for reporting, to [@wunter8](https://github.com/wunter8) for implementing, and to the Sprig team for their work) + ### ntfy server v2.13.0 Released July 10, 2025 @@ -1452,14 +1468,6 @@ and the [ntfy Android app](https://github.com/binwiederhier/ntfy-android/release ## Not released yet -### ntfy server v2.14.0 (UNRELEASED) - -**Features:** - -* [Declarative users](config.md#users-via-the-config), [declarative ACL entries](config.md#acl-entries-via-the-config) and [declarative tokens](config.md#tokens-via-the-config) ([#464](https://github.com/binwiederhier/ntfy/issues/464), [#1384](https://github.com/binwiederhier/ntfy/pull/1384), thanks to [pinpox](https://github.com/pinpox) for reporting, to [@wunter8](https://github.com/wunter8) for reviewing) -* [Pre-defined templates](publish.md#pre-defined-templates) and [custom templates](publish.md#custom-templates) for enhanced JSON webhook support ([#1390](https://github.com/binwiederhier/ntfy/pull/1390)) -* Support of advanced [template functions](publish.md#template-functions) based on the [Sprig](https://github.com/Masterminds/sprig) library ([#1121](https://github.com/binwiederhier/ntfy/issues/1121), thanks to [@davidatkinsondoyle](https://github.com/davidatkinsondoyle) for reporting, to [@wunter8](https://github.com/wunter8) for implementing, and to the Sprig team for their work) - ### ntfy Android app v1.16.1 (UNRELEASED) **Features:** diff --git a/go.mod b/go.mod index a191fb47..35ff59c7 100644 --- a/go.mod +++ b/go.mod @@ -30,10 +30,10 @@ replace github.com/emersion/go-smtp => github.com/emersion/go-smtp v0.17.0 // Pi require github.com/pkg/errors v0.9.1 // indirect require ( - firebase.google.com/go/v4 v4.17.0 + firebase.google.com/go/v4 v4.18.0 github.com/SherClockHolmes/webpush-go v1.4.0 github.com/microcosm-cc/bluemonday v1.0.27 - github.com/prometheus/client_golang v1.22.0 + github.com/prometheus/client_golang v1.23.0 github.com/stripe/stripe-go/v74 v74.30.0 golang.org/x/text v0.27.0 ) @@ -61,7 +61,7 @@ require ( github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/go-jose/go-jose/v4 v4.1.1 // indirect + github.com/go-jose/go-jose/v4 v4.1.2 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect @@ -95,9 +95,9 @@ require ( golang.org/x/net v0.42.0 // indirect golang.org/x/sys v0.34.0 // indirect google.golang.org/appengine/v2 v2.0.6 // indirect - google.golang.org/genproto v0.0.0-20250728155136-f173205681a0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 // indirect + google.golang.org/genproto v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b // indirect google.golang.org/grpc v1.74.2 // indirect google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 003dd999..9f12808a 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,8 @@ cloud.google.com/go/storage v1.56.0 h1:iixmq2Fse2tqxMbWhLWC9HfBj1qdxqAmiK8/eqtsL cloud.google.com/go/storage v1.56.0/go.mod h1:Tpuj6t4NweCLzlNbw9Z9iwxEkrSem20AetIeH/shgVU= cloud.google.com/go/trace v1.11.6 h1:2O2zjPzqPYAHrn3OKl029qlqG6W8ZdYaOWRyr8NgMT4= cloud.google.com/go/trace v1.11.6/go.mod h1:GA855OeDEBiBMzcckLPE2kDunIpC72N+Pq8WFieFjnI= -firebase.google.com/go/v4 v4.17.0 h1:Bih69QV/k0YKPA1qUX04ln0aPT9IERrAo2ezibcngzE= -firebase.google.com/go/v4 v4.17.0/go.mod h1:aAPJq/bOyb23tBlc1K6GR+2E8sOGAeJSc8wIJVgl9SM= +firebase.google.com/go/v4 v4.18.0 h1:S+g0P72oDGqOaG4wlLErX3zQmU9plVdu7j+Bc3R1qFw= +firebase.google.com/go/v4 v4.18.0/go.mod h1:P7UfBpzc8+Z3MckX79+zsWzKVfpGryr6HLbAe7gCWfs= github.com/AlekSi/pointer v1.2.0 h1:glcy/gc4h8HnG2Z3ZECSzZ1IX1x2JxRVuDzaJwQE0+w= github.com/AlekSi/pointer v1.2.0/go.mod h1:gZGfd3dpW4vEc/UlyfKKi1roIqcCgwOIvb0tSNSBle0= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= @@ -70,8 +70,8 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/gabriel-vasile/mimetype v1.4.9 h1:5k+WDwEsD9eTLL8Tz3L0VnmVh9QxGjRmjBvAG7U/oYY= github.com/gabriel-vasile/mimetype v1.4.9/go.mod h1:WnSQhFKJuBlRyLiKohA/2DtIlPFAbguNaG7QCHcyGok= -github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= -github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= +github.com/go-jose/go-jose/v4 v4.1.2 h1:TK/7NqRQZfgAh+Td8AlsrvtPoUyiHh0LqVvokh+1vHI= +github.com/go-jose/go-jose/v4 v4.1.2/go.mod h1:22cg9HWM1pOlnRiY+9cQYJ9XHmya1bYW8OeDM6Ku6Oo= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= @@ -127,8 +127,8 @@ github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.22.0 h1:rb93p9lokFEsctTys46VnV1kLCDpVZ0a/Y92Vm0Zc6Q= -github.com/prometheus/client_golang v1.22.0/go.mod h1:R7ljNsLXhuQXYZYtw6GAE9AZg8Y7vEW5scdCXrWRXC0= +github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= +github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= @@ -265,12 +265,12 @@ google.golang.org/api v0.244.0 h1:lpkP8wVibSKr++NCD36XzTk/IzeKJ3klj7vbj+XU5pE= google.golang.org/api v0.244.0/go.mod h1:dMVhVcylamkirHdzEBAIQWUCgqY885ivNeZYd7VAVr8= google.golang.org/appengine/v2 v2.0.6 h1:LvPZLGuchSBslPBp+LAhihBeGSiRh1myRoYK4NtuBIw= google.golang.org/appengine/v2 v2.0.6/go.mod h1:WoEXGoXNfa0mLvaH5sV3ZSGXwVmy8yf7Z1JKf3J3wLI= -google.golang.org/genproto v0.0.0-20250728155136-f173205681a0 h1:btBcgujH2+KIWEfz0s7Cdtt9R7hpwM4SAEXAdXf/ddw= -google.golang.org/genproto v0.0.0-20250728155136-f173205681a0/go.mod h1:Q4yZQ3kmmIyg6HsMjCGx2vQ8gzN+dntaPmFWz6Zj0fo= -google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8= -google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0 h1:MAKi5q709QWfnkkpNQ0M12hYJ1+e8qYVDyowc4U1XZM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250728155136-f173205681a0/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= +google.golang.org/genproto v0.0.0-20250804133106-a7a43d27e69b h1:eZTgydvqZO44zyTZAvMaSyAxccZZdraiSAGvqOczVvk= +google.golang.org/genproto v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:suyz2QBHQKlGIF92HEEsCfO1SwxXdk7PFLz+Zd9Uah4= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b h1:ULiyYQ0FdsJhwwZUwbaXpZF5yUE3h+RA+gxvBu37ucc= +google.golang.org/genproto/googleapis/api v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:oDOGiMSXHL4sDTJvFvIB9nRQCGdLP1o/iVaqQK8zB+M= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b h1:zPKJod4w6F1+nRGDI9ubnXYhU9NSWoFAijkHkUXeTK8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250804133106-a7a43d27e69b/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= diff --git a/web/package-lock.json b/web/package-lock.json index cfdb5d98..63234265 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -3066,13 +3066,13 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.28.2" } }, "node_modules/@types/estree": { @@ -3819,9 +3819,9 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.44.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.44.0.tgz", - "integrity": "sha512-JepmAj2zfl6ogy34qfWtcE7nHKAJnKsQFRn++scjVS2bZFllwptzw61BZcZFYBPpUznLfAvh0LGhxKppk04ClA==", + "version": "3.45.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.0.tgz", + "integrity": "sha512-gRoVMBawZg0OnxaVv3zpqLLxaHmsubEGyTnqdpI/CEBvX4JadI1dMSHxagThprYRtSVbuQxvi6iUatdPxohHpA==", "dev": true, "license": "MIT", "dependencies": { @@ -4112,9 +4112,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.193", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.193.tgz", - "integrity": "sha512-eePuBZXM9OVCwfYUhd2OzESeNGnWmLyeu0XAEjf7xjijNjHFdeJSzuRUGN4ueT2tEYo5YqjHramKEFxz67p3XA==", + "version": "1.5.195", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.195.tgz", + "integrity": "sha512-URclP0iIaDUzqcAyV1v2PgduJ9N0IdXmWsnPzPfelvBmjmZzEy6xJcjb1cXj+TbYqXgtLrjHEoaSIdTYhw4ezg==", "dev": true, "license": "ISC" }, @@ -6045,16 +6045,15 @@ } }, "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", + "async": "^3.2.6", "filelist": "^1.0.4", - "minimatch": "^3.1.2" + "picocolors": "^1.1.1" }, "bin": { "jake": "bin/cli.js" From 4225ce2f426c72d3a02f1f9d431de87c7f5e2c54 Mon Sep 17 00:00:00 2001 From: binwiederhier Date: Tue, 5 Aug 2025 10:12:53 +0200 Subject: [PATCH 6/6] Release notes --- docs/releases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/releases.md b/docs/releases.md index a78c6e7c..a77c6b1f 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -14,7 +14,7 @@ will always remain open source. **Features:** -* [Declarative users](config.md#users-via-the-config), [declarative ACL entries](config.md#acl-entries-via-the-config) and [declarative tokens](config.md#tokens-via-the-config) ([#464](https://github.com/binwiederhier/ntfy/issues/464), [#1384](https://github.com/binwiederhier/ntfy/pull/1384), thanks to [pinpox](https://github.com/pinpox) for reporting, to [@wunter8](https://github.com/wunter8) for reviewing) +* [Declarative users](config.md#users-via-the-config), [declarative ACL entries](config.md#acl-entries-via-the-config) and [declarative tokens](config.md#tokens-via-the-config) ([#464](https://github.com/binwiederhier/ntfy/issues/464), [#1384](https://github.com/binwiederhier/ntfy/pull/1384), [#1413](https://github.com/binwiederhier/ntfy/pull/1413), thanks to [pinpox](https://github.com/pinpox) for reporting, to [@wunter8](https://github.com/wunter8) for reviewing and implementing parts of it) * [Pre-defined templates](publish.md#pre-defined-templates) and [custom templates](publish.md#custom-templates) for enhanced JSON webhook support ([#1390](https://github.com/binwiederhier/ntfy/pull/1390)) * Support of advanced [template functions](publish.md#template-functions) based on the [Sprig](https://github.com/Masterminds/sprig) library ([#1121](https://github.com/binwiederhier/ntfy/issues/1121), thanks to [@davidatkinsondoyle](https://github.com/davidatkinsondoyle) for reporting, to [@wunter8](https://github.com/wunter8) for implementing, and to the Sprig team for their work)