Restructure limits

This commit is contained in:
binwiederhier 2022-12-19 16:22:13 -05:00
parent 6598ce2fe4
commit 84785b7a60
7 changed files with 105 additions and 74 deletions

View file

@ -773,7 +773,7 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message,
contentLengthStr := r.Header.Get("Content-Length")
if contentLengthStr != "" { // Early "do-not-trust" check, hard limit see below
contentLength, err := strconv.ParseInt(contentLengthStr, 10, 64)
if err == nil && (contentLength > visitorStats.VisitorAttachmentBytesRemaining || contentLength > s.config.AttachmentFileSizeLimit) {
if err == nil && (contentLength > visitorStats.AttachmentTotalSizeRemaining || contentLength > s.config.AttachmentFileSizeLimit) {
return errHTTPEntityTooLargeAttachmentTooLarge
}
}
@ -791,7 +791,7 @@ func (s *Server) handleBodyAsAttachment(r *http.Request, v *visitor, m *message,
if m.Message == "" {
m.Message = fmt.Sprintf(defaultAttachmentMessage, m.Attachment.Name)
}
m.Attachment.Size, err = s.fileCache.Write(m.ID, body, v.BandwidthLimiter(), util.NewFixedLimiter(visitorStats.VisitorAttachmentBytesRemaining))
m.Attachment.Size, err = s.fileCache.Write(m.ID, body, v.BandwidthLimiter(), util.NewFixedLimiter(visitorStats.AttachmentTotalSizeRemaining))
if err == util.ErrLimitReached {
return errHTTPEntityTooLargeAttachmentTooLarge
} else if err != nil {

View file

@ -40,7 +40,21 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
return err
}
response := &apiAccountSettingsResponse{
Usage: &apiAccountStats{},
Stats: &apiAccountStats{
Messages: stats.Messages,
MessagesRemaining: stats.MessagesRemaining,
Emails: stats.Emails,
EmailsRemaining: stats.EmailsRemaining,
AttachmentTotalSize: stats.AttachmentTotalSize,
AttachmentTotalSizeRemaining: stats.AttachmentTotalSizeRemaining,
},
Limits: &apiAccountLimits{
Basis: stats.Basis,
Messages: stats.MessagesLimit,
Emails: stats.EmailsLimit,
AttachmentTotalSize: stats.AttachmentTotalSizeLimit,
AttachmentFileSize: stats.AttachmentFileSizeLimit,
},
}
if v.user != nil {
response.Username = v.user.Name
@ -57,62 +71,31 @@ func (s *Server) handleAccountGet(w http.ResponseWriter, r *http.Request, v *vis
}
}
if v.user.Plan != nil {
response.Usage.Basis = "account"
response.Plan = &apiAccountSettingsPlan{
response.Plan = &apiAccountPlan{
Code: v.user.Plan.Code,
Upgradable: v.user.Plan.Upgradable,
}
response.Limits = &apiAccountLimits{
MessagesLimit: v.user.Plan.MessageLimit,
EmailsLimit: v.user.Plan.EmailsLimit,
AttachmentFileSizeLimit: v.user.Plan.AttachmentFileSizeLimit,
AttachmentTotalSizeLimit: v.user.Plan.AttachmentTotalSizeLimit,
}
} else {
if v.user.Role == auth.RoleAdmin {
response.Usage.Basis = "account"
response.Plan = &apiAccountSettingsPlan{
response.Plan = &apiAccountPlan{
Code: string(auth.PlanUnlimited),
Upgradable: false,
}
response.Limits = &apiAccountLimits{
MessagesLimit: 0,
EmailsLimit: 0,
AttachmentFileSizeLimit: 0,
AttachmentTotalSizeLimit: 0,
}
} else {
response.Usage.Basis = "ip"
response.Plan = &apiAccountSettingsPlan{
response.Plan = &apiAccountPlan{
Code: string(auth.PlanDefault),
Upgradable: true,
}
response.Limits = &apiAccountLimits{
MessagesLimit: int64(s.config.VisitorRequestLimitBurst),
EmailsLimit: int64(s.config.VisitorEmailLimitBurst),
AttachmentFileSizeLimit: s.config.AttachmentFileSizeLimit,
AttachmentTotalSizeLimit: s.config.VisitorAttachmentTotalSizeLimit,
}
}
}
} else {
response.Username = auth.Everyone
response.Role = string(auth.RoleAnonymous)
response.Usage.Basis = "ip"
response.Plan = &apiAccountSettingsPlan{
response.Plan = &apiAccountPlan{
Code: string(auth.PlanNone),
Upgradable: true,
}
response.Limits = &apiAccountLimits{
MessagesLimit: int64(s.config.VisitorRequestLimitBurst),
EmailsLimit: int64(s.config.VisitorEmailLimitBurst),
AttachmentFileSizeLimit: s.config.AttachmentFileSizeLimit,
AttachmentTotalSizeLimit: s.config.VisitorAttachmentTotalSizeLimit,
}
}
response.Usage.Messages = stats.Messages
response.Usage.Emails = stats.Emails
response.Usage.AttachmentsSize = stats.AttachmentBytes
if err := json.NewEncoder(w).Encode(response); err != nil {
return err
}

View file

@ -224,23 +224,26 @@ type apiAccountTokenResponse struct {
Token string `json:"token"`
}
type apiAccountSettingsPlan struct {
type apiAccountPlan struct {
Code string `json:"code"`
Upgradable bool `json:"upgradable"`
}
type apiAccountLimits struct {
MessagesLimit int64 `json:"messages"`
EmailsLimit int64 `json:"emails"`
AttachmentFileSizeLimit int64 `json:"attachment_file_size"`
AttachmentTotalSizeLimit int64 `json:"attachment_total_size"`
Basis string `json:"basis"` // "ip", "role" or "plan"
Messages int64 `json:"messages"`
Emails int64 `json:"emails"`
AttachmentTotalSize int64 `json:"attachment_total_size"`
AttachmentFileSize int64 `json:"attachment_file_size"`
}
type apiAccountStats struct {
Basis string `json:"basis"` // "ip" or "account"
Messages int64 `json:"messages"`
Emails int64 `json:"emails"`
AttachmentsSize int64 `json:"attachments_size"`
Messages int64 `json:"messages"`
MessagesRemaining int64 `json:"messages_remaining"`
Emails int64 `json:"emails"`
EmailsRemaining int64 `json:"emails_remaining"`
AttachmentTotalSize int64 `json:"attachment_total_size"`
AttachmentTotalSizeRemaining int64 `json:"attachment_total_size_remaining"`
}
type apiAccountSettingsResponse struct {
@ -249,7 +252,7 @@ type apiAccountSettingsResponse struct {
Language string `json:"language,omitempty"`
Notification *auth.UserNotificationPrefs `json:"notification,omitempty"`
Subscriptions []*auth.UserSubscription `json:"subscriptions,omitempty"`
Plan *apiAccountSettingsPlan `json:"plan,omitempty"`
Plan *apiAccountPlan `json:"plan,omitempty"`
Limits *apiAccountLimits `json:"limits,omitempty"`
Usage *apiAccountStats `json:"usage,omitempty"`
Stats *apiAccountStats `json:"stats,omitempty"`
}

View file

@ -40,17 +40,27 @@ type visitor struct {
}
type visitorStats struct {
Messages int64
Emails int64
AttachmentBytes int64
Basis string // "ip", "role" or "plan"
Messages int64
MessagesLimit int64
MessagesRemaining int64
Emails int64
EmailsLimit int64
EmailsRemaining int64
AttachmentTotalSize int64
AttachmentTotalSizeLimit int64
AttachmentTotalSizeRemaining int64
AttachmentFileSizeLimit int64
}
func newVisitor(conf *Config, messageCache *messageCache, ip netip.Addr, user *auth.User) *visitor {
var requestLimiter *rate.Limiter
var requestLimiter, emailsLimiter *rate.Limiter
if user != nil && user.Plan != nil {
requestLimiter = rate.NewLimiter(rate.Limit(user.Plan.MessageLimit)*rate.Every(24*time.Hour), conf.VisitorRequestLimitBurst)
requestLimiter = rate.NewLimiter(dailyLimitToRate(user.Plan.MessagesLimit), conf.VisitorRequestLimitBurst)
emailsLimiter = rate.NewLimiter(dailyLimitToRate(user.Plan.EmailsLimit), conf.VisitorEmailLimitBurst)
} else {
requestLimiter = rate.NewLimiter(rate.Every(conf.VisitorRequestLimitReplenish), conf.VisitorRequestLimitBurst)
emailsLimiter = rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst)
}
return &visitor{
config: conf,
@ -60,7 +70,7 @@ func newVisitor(conf *Config, messageCache *messageCache, ip netip.Addr, user *a
messages: 0, // TODO
emails: 0, // TODO
requestLimiter: requestLimiter,
emailsLimiter: rate.NewLimiter(rate.Every(conf.VisitorEmailLimitReplenish), conf.VisitorEmailLimitBurst),
emailsLimiter: emailsLimiter,
subscriptionLimiter: util.NewFixedLimiter(int64(conf.VisitorSubscriptionLimit)),
bandwidthLimiter: util.NewBytesLimiter(conf.VisitorAttachmentDailyBandwidthLimit, 24*time.Hour),
firebase: time.Unix(0, 0),
@ -147,9 +157,46 @@ func (v *visitor) Stats() (*visitorStats, error) {
}
v.mu.Lock()
defer v.mu.Unlock()
return &visitorStats{
Messages: v.messages,
Emails: v.emails,
AttachmentBytes: attachmentsBytesUsed,
}, nil
stats := &visitorStats{}
if v.user != nil && v.user.Role == auth.RoleAdmin {
stats.Basis = "role"
stats.MessagesLimit = 0
stats.EmailsLimit = 0
stats.AttachmentTotalSizeLimit = 0
stats.AttachmentFileSizeLimit = 0
} else if v.user != nil && v.user.Plan != nil {
stats.Basis = "plan"
stats.MessagesLimit = v.user.Plan.MessagesLimit
stats.EmailsLimit = v.user.Plan.EmailsLimit
stats.AttachmentTotalSizeLimit = v.user.Plan.AttachmentTotalSizeLimit
stats.AttachmentFileSizeLimit = v.user.Plan.AttachmentFileSizeLimit
} else {
stats.Basis = "ip"
stats.MessagesLimit = replenishDurationToDailyLimit(v.config.VisitorRequestLimitReplenish)
stats.EmailsLimit = replenishDurationToDailyLimit(v.config.VisitorEmailLimitReplenish)
stats.AttachmentTotalSizeLimit = v.config.AttachmentTotalSizeLimit
stats.AttachmentFileSizeLimit = v.config.AttachmentFileSizeLimit
}
stats.Messages = v.messages
stats.MessagesRemaining = zeroIfNegative(stats.MessagesLimit - stats.MessagesLimit)
stats.Emails = v.emails
stats.EmailsRemaining = zeroIfNegative(stats.EmailsLimit - stats.EmailsRemaining)
stats.AttachmentTotalSize = attachmentsBytesUsed
stats.AttachmentTotalSizeRemaining = zeroIfNegative(stats.AttachmentTotalSizeLimit - stats.AttachmentTotalSize)
return stats, nil
}
func zeroIfNegative(value int64) int64 {
if value < 0 {
return 0
}
return value
}
func replenishDurationToDailyLimit(duration time.Duration) int64 {
return int64(24 * time.Hour / duration)
}
func dailyLimitToRate(limit int64) rate.Limit {
return rate.Limit(limit) * rate.Every(24*time.Hour)
}