Reject reservation limits in endpoint
parent
1bc40693bb
commit
a51d95743a
|
@ -69,8 +69,9 @@ var (
|
||||||
errHTTPTooManyRequestsLimitEmails = &errHTTP{42902, http.StatusTooManyRequests, "limit reached: too many emails, please be nice", "https://ntfy.sh/docs/publish/#limitations"}
|
errHTTPTooManyRequestsLimitEmails = &errHTTP{42902, http.StatusTooManyRequests, "limit reached: too many emails, please be nice", "https://ntfy.sh/docs/publish/#limitations"}
|
||||||
errHTTPTooManyRequestsLimitSubscriptions = &errHTTP{42903, http.StatusTooManyRequests, "limit reached: too many active subscriptions, please be nice", "https://ntfy.sh/docs/publish/#limitations"}
|
errHTTPTooManyRequestsLimitSubscriptions = &errHTTP{42903, http.StatusTooManyRequests, "limit reached: too many active subscriptions, please be nice", "https://ntfy.sh/docs/publish/#limitations"}
|
||||||
errHTTPTooManyRequestsLimitTotalTopics = &errHTTP{42904, http.StatusTooManyRequests, "limit reached: the total number of topics on the server has been reached, please contact the admin", "https://ntfy.sh/docs/publish/#limitations"}
|
errHTTPTooManyRequestsLimitTotalTopics = &errHTTP{42904, http.StatusTooManyRequests, "limit reached: the total number of topics on the server has been reached, please contact the admin", "https://ntfy.sh/docs/publish/#limitations"}
|
||||||
errHTTPTooManyRequestsAttachmentBandwidthLimit = &errHTTP{42905, http.StatusTooManyRequests, "too many requests: daily bandwidth limit reached", "https://ntfy.sh/docs/publish/#limitations"}
|
errHTTPTooManyRequestsLimitAttachmentBandwidth = &errHTTP{42905, http.StatusTooManyRequests, "limit reached: daily bandwidth", "https://ntfy.sh/docs/publish/#limitations"}
|
||||||
errHTTPTooManyRequestsAccountCreateLimit = &errHTTP{42906, http.StatusTooManyRequests, "too many requests: daily account creation limit reached", "https://ntfy.sh/docs/publish/#limitations"} // FIXME document limit
|
errHTTPTooManyRequestsLimitAccountCreation = &errHTTP{42906, http.StatusTooManyRequests, "limit reached: too many accounts created", "https://ntfy.sh/docs/publish/#limitations"} // FIXME document limit
|
||||||
|
errHTTPTooManyRequestsLimitReservations = &errHTTP{42907, http.StatusTooManyRequests, "limit reached: too many topic reservations for this user", ""}
|
||||||
errHTTPInternalError = &errHTTP{50001, http.StatusInternalServerError, "internal server error", ""}
|
errHTTPInternalError = &errHTTP{50001, http.StatusInternalServerError, "internal server error", ""}
|
||||||
errHTTPInternalErrorInvalidPath = &errHTTP{50002, http.StatusInternalServerError, "internal server error: invalid path", ""}
|
errHTTPInternalErrorInvalidPath = &errHTTP{50002, http.StatusInternalServerError, "internal server error: invalid path", ""}
|
||||||
errHTTPInternalErrorMissingBaseURL = &errHTTP{50003, http.StatusInternalServerError, "internal server error: base-url must be be configured for this feature", "https://ntfy.sh/docs/config/"}
|
errHTTPInternalErrorMissingBaseURL = &errHTTP{50003, http.StatusInternalServerError, "internal server error: base-url must be be configured for this feature", "https://ntfy.sh/docs/config/"}
|
||||||
|
|
|
@ -36,26 +36,31 @@ import (
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO
|
TODO
|
||||||
limits:
|
limits & rate limiting:
|
||||||
message cache duration
|
message cache duration
|
||||||
Keep 10000 messages or keep X days?
|
Keep 10000 messages or keep X days?
|
||||||
Attachment expiration based on plan
|
Attachment expiration based on plan
|
||||||
|
login/account endpoints
|
||||||
plan:
|
plan:
|
||||||
weirdness with admin and "default" account
|
weirdness with admin and "default" account
|
||||||
"account topic" sync mechanism
|
|
||||||
v.Info() endpoint double selects from DB
|
v.Info() endpoint double selects from DB
|
||||||
JS constants
|
|
||||||
purge accounts that were not logged into in X
|
purge accounts that were not logged into in X
|
||||||
reset daily limits for users
|
reset daily limits for users
|
||||||
|
Make sure account endpoints make sense for admins
|
||||||
UI:
|
UI:
|
||||||
- flicker of upgrade banner
|
- flicker of upgrade banner
|
||||||
|
- JS constants
|
||||||
|
- useContext for account
|
||||||
Sync:
|
Sync:
|
||||||
|
- "account topic" sync mechanism
|
||||||
- "mute" setting
|
- "mute" setting
|
||||||
- figure out what settings are "web" or "phone"
|
- figure out what settings are "web" or "phone"
|
||||||
rate limiting:
|
|
||||||
- login/account endpoints
|
|
||||||
Tests:
|
Tests:
|
||||||
|
- /access endpoints
|
||||||
- visitor with/without user
|
- visitor with/without user
|
||||||
|
Refactor:
|
||||||
|
- rename TopicsLimit -> ReservationsLimit
|
||||||
|
- rename /access -> /reservation
|
||||||
Later:
|
Later:
|
||||||
- Password reset
|
- Password reset
|
||||||
- Pricing
|
- Pricing
|
||||||
|
@ -496,7 +501,7 @@ func (s *Server) handleFile(w http.ResponseWriter, r *http.Request, v *visitor)
|
||||||
}
|
}
|
||||||
if r.Method == http.MethodGet {
|
if r.Method == http.MethodGet {
|
||||||
if err := v.BandwidthLimiter().Allow(stat.Size()); err != nil {
|
if err := v.BandwidthLimiter().Allow(stat.Size()); err != nil {
|
||||||
return errHTTPTooManyRequestsAttachmentBandwidthLimit
|
return errHTTPTooManyRequestsLimitAttachmentBandwidth
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size()))
|
w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size()))
|
||||||
|
|
|
@ -2,6 +2,7 @@ package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"heckel.io/ntfy/user"
|
"heckel.io/ntfy/user"
|
||||||
"heckel.io/ntfy/util"
|
"heckel.io/ntfy/util"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -29,7 +30,7 @@ func (s *Server) handleAccountCreate(w http.ResponseWriter, r *http.Request, v *
|
||||||
return errHTTPConflictUserExists
|
return errHTTPConflictUserExists
|
||||||
}
|
}
|
||||||
if v.accountLimiter != nil && !v.accountLimiter.Allow() {
|
if v.accountLimiter != nil && !v.accountLimiter.Allow() {
|
||||||
return errHTTPTooManyRequestsAccountCreateLimit
|
return errHTTPTooManyRequestsLimitAccountCreation
|
||||||
}
|
}
|
||||||
if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser); err != nil { // TODO this should return a User
|
if err := s.userManager.AddUser(newAccount.Username, newAccount.Password, user.RoleUser); err != nil { // TODO this should return a User
|
||||||
return err
|
return err
|
||||||
|
@ -331,6 +332,15 @@ func (s *Server) handleAccountAccessAdd(w http.ResponseWriter, r *http.Request,
|
||||||
if !topicRegex.MatchString(req.Topic) {
|
if !topicRegex.MatchString(req.Topic) {
|
||||||
return errHTTPBadRequestTopicInvalid
|
return errHTTPBadRequestTopicInvalid
|
||||||
}
|
}
|
||||||
|
if v.user.Plan == nil {
|
||||||
|
return errors.New("no plan") // FIXME there should always be a plan!
|
||||||
|
}
|
||||||
|
reservations, err := s.userManager.ReservationsCount(v.user.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
} else if reservations >= v.user.Plan.TopicsLimit {
|
||||||
|
return errHTTPTooManyRequestsLimitReservations // FIXME test this
|
||||||
|
}
|
||||||
if err := s.userManager.CheckAllowAccess(v.user.Name, req.Topic); err != nil {
|
if err := s.userManager.CheckAllowAccess(v.user.Name, req.Topic); err != nil {
|
||||||
return errHTTPConflictTopicReserved
|
return errHTTPConflictTopicReserved
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,6 +140,11 @@ const (
|
||||||
AND a_user.owner_user_id = (SELECT id FROM user WHERE user = ?)
|
AND a_user.owner_user_id = (SELECT id FROM user WHERE user = ?)
|
||||||
ORDER BY a_user.topic
|
ORDER BY a_user.topic
|
||||||
`
|
`
|
||||||
|
selectUserReservationsCountQuery = `
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM user_access
|
||||||
|
WHERE user_id = owner_user_id AND owner_user_id = (SELECT id FROM user WHERE user = ?)
|
||||||
|
`
|
||||||
selectOtherAccessCountQuery = `
|
selectOtherAccessCountQuery = `
|
||||||
SELECT COUNT(*)
|
SELECT COUNT(*)
|
||||||
FROM user_access
|
FROM user_access
|
||||||
|
@ -599,6 +604,23 @@ func (a *Manager) Reservations(username string) ([]Reservation, error) {
|
||||||
return reservations, nil
|
return reservations, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ReservationsCount returns the number of reservations owned by this user
|
||||||
|
func (a *Manager) ReservationsCount(username string) (int64, error) {
|
||||||
|
rows, err := a.db.Query(selectUserReservationsCountQuery, username)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
if !rows.Next() {
|
||||||
|
return 0, errNoRows
|
||||||
|
}
|
||||||
|
var count int64
|
||||||
|
if err := rows.Scan(&count); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
// ChangePassword changes a user's password
|
// ChangePassword changes a user's password
|
||||||
func (a *Manager) ChangePassword(username, password string) error {
|
func (a *Manager) ChangePassword(username, password string) error {
|
||||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
|
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcryptCost)
|
||||||
|
|
Loading…
Reference in New Issue