1
0
Fork 0
mirror of https://github.com/binwiederhier/ntfy.git synced 2024-11-01 01:21:15 +01:00
ntfy/server/server_account.go

440 lines
14 KiB
Go
Raw Normal View History

2022-12-16 04:07:04 +01:00
package server
import (
"encoding/json"
"heckel.io/ntfy/log"
"heckel.io/ntfy/user"
2022-12-16 04:07:04 +01:00
"heckel.io/ntfy/util"
"net/http"
"strings"
2022-12-16 04:07:04 +01:00
)
const (
subscriptionIDLength = 16
subscriptionIDPrefix = "su_"
syncTopicAccountSyncEvent = "sync"
)
2022-12-16 04:07:04 +01:00
func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *visitor) error {
admin := v.user != nil && v.user.Role == user.RoleAdmin
2022-12-24 18:10:51 +01:00
if !admin {
if !s.config.EnableSignup {
return errHTTPBadRequestSignupNotEnabled
} else if v.user != nil {
return errHTTPUnauthorized // Cannot create account from user context
}
if !v.AccountCreationAllowed() {
2023-01-27 04:57:18 +01:00
return errHTTPTooManyRequestsLimitAccountCreation
}
2022-12-16 04:07:04 +01:00
}
2022-12-29 15:57:42 +01:00
newAccount, err := readJSONWithLimit[apiAccountCreateRequest](r.Body, jsonBodyBytesLimit)
2022-12-16 04:07:04 +01:00
if err != nil {
return err
}
if existingUser, _ := s.userManager.User(newAccount.Username); existingUser != nil {
2022-12-22 03:55:39 +01:00
return errHTTPConflictUserExists
}
2023-01-23 04:21:30 +01:00
if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser); err != nil { // TODO this should return a User
2022-12-16 04:07:04 +01:00
return err
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, newSuccessResponse())
2022-12-16 04:07:04 +01:00
}
2023-01-02 02:42:33 +01:00
func (s *Server) handleAccountGet(w http.ResponseWriter, _ *http.Request, v *visitor) error {
2023-01-09 21:40:46 +01:00
info, err := v.Info()
2022-12-17 21:17:52 +01:00
if err != nil {
return err
}
2023-01-09 21:40:46 +01:00
limits, stats := info.Limits, info.Stats
2022-12-28 04:14:14 +01:00
response := &apiAccountResponse{
2023-01-09 21:40:46 +01:00
Limits: &apiAccountLimits{
Basis: string(limits.Basis),
2023-01-27 04:57:18 +01:00
Messages: limits.MessageLimit,
MessagesExpiryDuration: int64(limits.MessageExpiryDuration.Seconds()),
Emails: limits.EmailLimit,
2023-01-09 21:40:46 +01:00
Reservations: limits.ReservationsLimit,
AttachmentTotalSize: limits.AttachmentTotalSizeLimit,
AttachmentFileSize: limits.AttachmentFileSizeLimit,
AttachmentExpiryDuration: int64(limits.AttachmentExpiryDuration.Seconds()),
AttachmentBandwidth: limits.AttachmentBandwidthLimit,
2023-01-09 21:40:46 +01:00
},
2022-12-19 22:22:13 +01:00
Stats: &apiAccountStats{
Messages: stats.Messages,
MessagesRemaining: stats.MessagesRemaining,
Emails: stats.Emails,
EmailsRemaining: stats.EmailsRemaining,
Reservations: stats.Reservations,
ReservationsRemaining: stats.ReservationsRemaining,
2022-12-19 22:22:13 +01:00
AttachmentTotalSize: stats.AttachmentTotalSize,
AttachmentTotalSizeRemaining: stats.AttachmentTotalSizeRemaining,
},
2022-12-17 21:17:52 +01:00
}
if v.user != nil {
response.Username = v.user.Name
response.Role = string(v.user.Role)
2023-01-10 03:53:21 +01:00
response.SyncTopic = v.user.SyncTopic
2022-12-17 21:17:52 +01:00
if v.user.Prefs != nil {
if v.user.Prefs.Language != nil {
response.Language = *v.user.Prefs.Language
2022-12-17 21:17:52 +01:00
}
if v.user.Prefs.Notification != nil {
response.Notification = v.user.Prefs.Notification
}
if v.user.Prefs.Subscriptions != nil {
response.Subscriptions = v.user.Prefs.Subscriptions
}
}
if v.user.Tier != nil {
response.Tier = &apiAccountTier{
2023-01-09 21:40:46 +01:00
Code: v.user.Tier.Code,
Name: v.user.Tier.Name,
}
2022-12-17 21:17:52 +01:00
}
2023-01-16 05:29:46 +01:00
if v.user.Billing.StripeCustomerID != "" {
response.Billing = &apiAccountBilling{
Customer: true,
Subscription: v.user.Billing.StripeSubscriptionID != "",
Status: string(v.user.Billing.StripeSubscriptionStatus),
PaidUntil: v.user.Billing.StripeSubscriptionPaidUntil.Unix(),
2023-01-16 16:35:12 +01:00
CancelAt: v.user.Billing.StripeSubscriptionCancelAt.Unix(),
2023-01-16 05:29:46 +01:00
}
}
2023-01-03 02:08:37 +01:00
reservations, err := s.userManager.Reservations(v.user.Name)
if err != nil {
return err
}
if len(reservations) > 0 {
response.Reservations = make([]*apiAccountReservation, 0)
for _, r := range reservations {
response.Reservations = append(response.Reservations, &apiAccountReservation{
Topic: r.Topic,
Everyone: r.Everyone.String(),
2023-01-03 02:08:37 +01:00
})
2023-01-01 21:21:43 +01:00
}
}
2022-12-17 21:17:52 +01:00
} else {
response.Username = user.Everyone
response.Role = string(user.RoleAnonymous)
2022-12-17 21:17:52 +01:00
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, response)
2022-12-17 21:17:52 +01:00
}
2023-01-23 04:21:30 +01:00
func (s *Server) handleAccountDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiAccountDeleteRequest](r.Body, jsonBodyBytesLimit)
if err != nil {
return err
} else if req.Password == "" {
return errHTTPBadRequest
}
if _, err := s.userManager.Authenticate(v.user.Name, req.Password); err != nil {
return errHTTPBadRequestIncorrectPasswordConfirmation
}
if v.user.Billing.StripeSubscriptionID != "" {
2023-01-23 04:21:30 +01:00
log.Info("%s Canceling billing subscription %s", logHTTPPrefix(v, r), v.user.Billing.StripeSubscriptionID)
if _, err := s.stripe.CancelSubscription(v.user.Billing.StripeSubscriptionID); err != nil {
2023-01-23 04:21:30 +01:00
return err
}
2023-01-19 20:03:39 +01:00
}
if err := s.maybeRemoveMessagesAndExcessReservations(logHTTPPrefix(v, r), v.user, 0); err != nil {
return err
}
2023-01-23 04:21:30 +01:00
log.Info("%s Marking user %s as deleted", logHTTPPrefix(v, r), v.user.Name)
if err := s.userManager.MarkUserRemoved(v.user); err != nil {
2022-12-16 04:07:04 +01:00
return err
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, newSuccessResponse())
2022-12-16 04:07:04 +01:00
}
func (s *Server) handleAccountPasswordChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
req, err := readJSONWithLimit[apiAccountPasswordChangeRequest](r.Body, jsonBodyBytesLimit)
2022-12-16 04:07:04 +01:00
if err != nil {
return err
} else if req.Password == "" || req.NewPassword == "" {
return errHTTPBadRequest
2022-12-16 04:07:04 +01:00
}
if _, err := s.userManager.Authenticate(v.user.Name, req.Password); err != nil {
return errHTTPBadRequestIncorrectPasswordConfirmation
}
if err := s.userManager.ChangePassword(v.user.Name, req.NewPassword); err != nil {
2022-12-16 04:07:04 +01:00
return err
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, newSuccessResponse())
2022-12-16 04:07:04 +01:00
}
2023-01-02 02:42:33 +01:00
func (s *Server) handleAccountTokenIssue(w http.ResponseWriter, _ *http.Request, v *visitor) error {
2022-12-16 04:07:04 +01:00
// TODO rate limit
token, err := s.userManager.CreateToken(v.user)
2022-12-16 04:07:04 +01:00
if err != nil {
return err
}
response := &apiAccountTokenResponse{
Token: token.Value,
2022-12-28 19:46:18 +01:00
Expires: token.Expires.Unix(),
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, response)
}
2023-01-02 02:42:33 +01:00
func (s *Server) handleAccountTokenExtend(w http.ResponseWriter, _ *http.Request, v *visitor) error {
// TODO rate limit
if v.user == nil {
return errHTTPUnauthorized
} else if v.user.Token == "" {
return errHTTPBadRequestNoTokenProvided
}
token, err := s.userManager.ExtendToken(v.user)
if err != nil {
return err
}
response := &apiAccountTokenResponse{
Token: token.Value,
2022-12-28 19:46:18 +01:00
Expires: token.Expires.Unix(),
2022-12-16 04:07:04 +01:00
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, response)
2022-12-16 04:07:04 +01:00
}
2023-01-02 02:42:33 +01:00
func (s *Server) handleAccountTokenDelete(w http.ResponseWriter, _ *http.Request, v *visitor) error {
2022-12-16 04:07:04 +01:00
// TODO rate limit
if v.user.Token == "" {
2022-12-29 15:57:42 +01:00
return errHTTPBadRequestNoTokenProvided
2022-12-16 04:07:04 +01:00
}
if err := s.userManager.RemoveToken(v.user); err != nil {
2022-12-16 04:07:04 +01:00
return err
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, newSuccessResponse())
2022-12-16 04:07:04 +01:00
}
func (s *Server) handleAccountSettingsChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
2022-12-29 15:57:42 +01:00
newPrefs, err := readJSONWithLimit[user.Prefs](r.Body, jsonBodyBytesLimit)
2022-12-16 04:07:04 +01:00
if err != nil {
return err
}
if v.user.Prefs == nil {
v.user.Prefs = &user.Prefs{}
2022-12-16 04:07:04 +01:00
}
prefs := v.user.Prefs
if newPrefs.Language != nil {
2022-12-16 04:07:04 +01:00
prefs.Language = newPrefs.Language
}
if newPrefs.Notification != nil {
if prefs.Notification == nil {
prefs.Notification = &user.NotificationPrefs{}
2022-12-16 04:07:04 +01:00
}
if newPrefs.Notification.DeleteAfter != nil {
2022-12-16 04:07:04 +01:00
prefs.Notification.DeleteAfter = newPrefs.Notification.DeleteAfter
}
if newPrefs.Notification.Sound != nil {
2022-12-16 04:07:04 +01:00
prefs.Notification.Sound = newPrefs.Notification.Sound
}
if newPrefs.Notification.MinPriority != nil {
2022-12-16 04:07:04 +01:00
prefs.Notification.MinPriority = newPrefs.Notification.MinPriority
}
}
if err := s.userManager.ChangeSettings(v.user); err != nil {
return err
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, newSuccessResponse())
2022-12-16 04:07:04 +01:00
}
func (s *Server) handleAccountSubscriptionAdd(w http.ResponseWriter, r *http.Request, v *visitor) error {
2022-12-29 15:57:42 +01:00
newSubscription, err := readJSONWithLimit[user.Subscription](r.Body, jsonBodyBytesLimit)
2022-12-16 04:07:04 +01:00
if err != nil {
return err
}
if v.user.Prefs == nil {
v.user.Prefs = &user.Prefs{}
2022-12-16 04:07:04 +01:00
}
newSubscription.ID = "" // Client cannot set ID
for _, subscription := range v.user.Prefs.Subscriptions {
if newSubscription.BaseURL == subscription.BaseURL && newSubscription.Topic == subscription.Topic {
2022-12-26 04:29:55 +01:00
newSubscription = subscription
2022-12-16 04:07:04 +01:00
break
}
}
if newSubscription.ID == "" {
newSubscription.ID = util.RandomStringPrefix(subscriptionIDPrefix, subscriptionIDLength)
2022-12-26 04:29:55 +01:00
v.user.Prefs.Subscriptions = append(v.user.Prefs.Subscriptions, newSubscription)
if err := s.userManager.ChangeSettings(v.user); err != nil {
2022-12-16 04:07:04 +01:00
return err
}
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, newSubscription)
2022-12-16 04:07:04 +01:00
}
2022-12-26 04:29:55 +01:00
func (s *Server) handleAccountSubscriptionChange(w http.ResponseWriter, r *http.Request, v *visitor) error {
2023-01-17 16:09:37 +01:00
matches := apiAccountSubscriptionSingleRegex.FindStringSubmatch(r.URL.Path)
2022-12-26 04:29:55 +01:00
if len(matches) != 2 {
2022-12-29 15:57:42 +01:00
return errHTTPInternalErrorInvalidPath
2022-12-26 04:29:55 +01:00
}
2022-12-29 15:57:42 +01:00
subscriptionID := matches[1]
updatedSubscription, err := readJSONWithLimit[user.Subscription](r.Body, jsonBodyBytesLimit)
2022-12-26 04:29:55 +01:00
if err != nil {
return err
}
if v.user.Prefs == nil || v.user.Prefs.Subscriptions == nil {
return errHTTPNotFound
}
var subscription *user.Subscription
for _, sub := range v.user.Prefs.Subscriptions {
if sub.ID == subscriptionID {
sub.DisplayName = updatedSubscription.DisplayName
subscription = sub
break
}
}
if subscription == nil {
return errHTTPNotFound
}
if err := s.userManager.ChangeSettings(v.user); err != nil {
return err
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, subscription)
2022-12-26 04:29:55 +01:00
}
2022-12-16 04:07:04 +01:00
func (s *Server) handleAccountSubscriptionDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
2023-01-17 16:09:37 +01:00
matches := apiAccountSubscriptionSingleRegex.FindStringSubmatch(r.URL.Path)
2022-12-16 04:07:04 +01:00
if len(matches) != 2 {
2022-12-29 15:57:42 +01:00
return errHTTPInternalErrorInvalidPath
2022-12-16 04:07:04 +01:00
}
subscriptionID := matches[1]
if v.user.Prefs == nil || v.user.Prefs.Subscriptions == nil {
return nil
}
newSubscriptions := make([]*user.Subscription, 0)
2022-12-16 04:07:04 +01:00
for _, subscription := range v.user.Prefs.Subscriptions {
if subscription.ID != subscriptionID {
newSubscriptions = append(newSubscriptions, subscription)
}
}
if len(newSubscriptions) < len(v.user.Prefs.Subscriptions) {
v.user.Prefs.Subscriptions = newSubscriptions
if err := s.userManager.ChangeSettings(v.user); err != nil {
2022-12-16 04:07:04 +01:00
return err
}
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, newSuccessResponse())
2022-12-16 04:07:04 +01:00
}
2022-12-30 20:20:48 +01:00
2023-01-12 16:50:09 +01:00
func (s *Server) handleAccountReservationAdd(w http.ResponseWriter, r *http.Request, v *visitor) error {
if v.user != nil && v.user.Role == user.RoleAdmin {
return errHTTPBadRequestMakesNoSenseForAdmin
}
2023-01-12 18:04:18 +01:00
req, err := readJSONWithLimit[apiAccountReservationRequest](r.Body, jsonBodyBytesLimit)
2022-12-30 20:20:48 +01:00
if err != nil {
return err
}
if !topicRegex.MatchString(req.Topic) {
return errHTTPBadRequestTopicInvalid
}
2023-01-06 16:45:38 +01:00
everyone, err := user.ParsePermission(req.Everyone)
if err != nil {
return errHTTPBadRequestPermissionInvalid
}
if v.user.Tier == nil {
2023-01-12 16:50:09 +01:00
return errHTTPUnauthorized
2023-01-06 03:15:10 +01:00
}
// CHeck if we are allowed to reserve this topic
2023-01-01 21:21:43 +01:00
if err := s.userManager.CheckAllowAccess(v.user.Name, req.Topic); err != nil {
return errHTTPConflictTopicReserved
}
2023-01-06 16:45:38 +01:00
hasReservation, err := s.userManager.HasReservation(v.user.Name, req.Topic)
if err != nil {
2023-01-06 16:45:38 +01:00
return err
}
if !hasReservation {
reservations, err := s.userManager.ReservationsCount(v.user.Name)
if err != nil {
return err
2023-01-27 04:57:18 +01:00
} else if reservations >= v.user.Tier.ReservationLimit {
2023-01-06 16:45:38 +01:00
return errHTTPTooManyRequestsLimitReservations
}
}
// Actually add the reservation
if err := s.userManager.AddReservation(v.user.Name, req.Topic, everyone); err != nil {
2023-01-01 21:21:43 +01:00
return err
}
// Kill existing subscribers
t, err := s.topicFromID(req.Topic)
if err != nil {
return err
}
t.CancelSubscribers(v.user.ID)
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, newSuccessResponse())
2023-01-01 21:21:43 +01:00
}
2023-01-12 16:50:09 +01:00
func (s *Server) handleAccountReservationDelete(w http.ResponseWriter, r *http.Request, v *visitor) error {
2023-01-17 16:09:37 +01:00
matches := apiAccountReservationSingleRegex.FindStringSubmatch(r.URL.Path)
2023-01-01 21:21:43 +01:00
if len(matches) != 2 {
return errHTTPInternalErrorInvalidPath
}
topic := matches[1]
if !topicRegex.MatchString(topic) {
return errHTTPBadRequestTopicInvalid
}
2023-01-06 16:45:38 +01:00
authorized, err := s.userManager.HasReservation(v.user.Name, topic)
2023-01-03 02:08:37 +01:00
if err != nil {
return err
2023-01-06 16:45:38 +01:00
} else if !authorized {
2023-01-01 21:21:43 +01:00
return errHTTPUnauthorized
}
if err := s.userManager.RemoveReservations(v.user.Name, topic); err != nil {
2022-12-30 20:20:48 +01:00
return err
}
2023-01-18 21:50:06 +01:00
return s.writeJSON(w, newSuccessResponse())
2022-12-30 20:20:48 +01:00
}
// maybeRemoveMessagesAndExcessReservations deletes topic reservations for the given user (if too many for tier),
// and marks associated messages for the topics as deleted. This also eventually deletes attachments.
// The process relies on the manager to perform the actual deletions (see runManager).
func (s *Server) maybeRemoveMessagesAndExcessReservations(logPrefix string, u *user.User, reservationsLimit int64) error {
reservations, err := s.userManager.Reservations(u.Name)
if err != nil {
return err
} else if int64(len(reservations)) <= reservationsLimit {
return nil
}
topics := make([]string, 0)
for i := int64(len(reservations)) - 1; i >= reservationsLimit; i-- {
topics = append(topics, reservations[i].Topic)
}
log.Info("%s Removing excess reservations for topics %s", logPrefix, strings.Join(topics, ", "))
if err := s.userManager.RemoveReservations(u.Name, topics...); err != nil {
return err
}
if err := s.messageCache.ExpireMessages(topics...); err != nil {
return err
}
return nil
}
func (s *Server) publishSyncEvent(v *visitor) error {
if v.user == nil || v.user.SyncTopic == "" {
return nil
}
log.Trace("Publishing sync event to user %s's sync topic %s", v.user.Name, v.user.SyncTopic)
syncTopic, err := s.topicFromID(v.user.SyncTopic)
if err != nil {
return err
}
messageBytes, err := json.Marshal(&apiAccountSyncTopicResponse{Event: syncTopicAccountSyncEvent})
if err != nil {
return err
}
m := newDefaultMessage(syncTopic.ID, string(messageBytes))
if err := syncTopic.Publish(v, m); err != nil {
return err
}
return nil
}
func (s *Server) publishSyncEventAsync(v *visitor) {
go func() {
u := v.User()
if u == nil || u.SyncTopic == "" {
return
}
if err := s.publishSyncEvent(v); err != nil {
log.Trace("Error publishing to user %s's sync topic %s: %s", u.Name, u.SyncTopic, err.Error())
}
}()
}