types.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. package user
  2. import (
  3. "errors"
  4. "github.com/stripe/stripe-go/v74"
  5. "heckel.io/ntfy/v2/log"
  6. "net/netip"
  7. "strings"
  8. "time"
  9. )
  10. // User is a struct that represents a user
  11. type User struct {
  12. ID string
  13. Name string
  14. Hash string // Password hash (bcrypt)
  15. Token string // Only set if token was used to log in
  16. Role Role
  17. Prefs *Prefs
  18. Tier *Tier
  19. Stats *Stats
  20. Billing *Billing
  21. SyncTopic string
  22. Provisioned bool // Whether the user was provisioned by the config file
  23. Deleted bool // Whether the user was soft-deleted
  24. }
  25. // TierID returns the ID of the User.Tier, or an empty string if the user has no tier,
  26. // or if the user itself is nil.
  27. func (u *User) TierID() string {
  28. if u == nil || u.Tier == nil {
  29. return ""
  30. }
  31. return u.Tier.ID
  32. }
  33. // IsAdmin returns true if the user is an admin
  34. func (u *User) IsAdmin() bool {
  35. return u != nil && u.Role == RoleAdmin
  36. }
  37. // IsUser returns true if the user is a regular user, not an admin
  38. func (u *User) IsUser() bool {
  39. return u != nil && u.Role == RoleUser
  40. }
  41. // Auther is an interface for authentication and authorization
  42. type Auther interface {
  43. // Authenticate checks username and password and returns a user if correct. The method
  44. // returns in constant-ish time, regardless of whether the user exists or the password is
  45. // correct or incorrect.
  46. Authenticate(username, password string) (*User, error)
  47. // Authorize returns nil if the given user has access to the given topic using the desired
  48. // permission. The user param may be nil to signal an anonymous user.
  49. Authorize(user *User, topic string, perm Permission) error
  50. }
  51. // Token represents a user token, including expiry date
  52. type Token struct {
  53. Value string
  54. Label string
  55. LastAccess time.Time
  56. LastOrigin netip.Addr
  57. Expires time.Time
  58. Provisioned bool
  59. }
  60. // TokenUpdate holds information about the last access time and origin IP address of a token
  61. type TokenUpdate struct {
  62. LastAccess time.Time
  63. LastOrigin netip.Addr
  64. }
  65. // Prefs represents a user's configuration settings
  66. type Prefs struct {
  67. Language *string `json:"language,omitempty"`
  68. Notification *NotificationPrefs `json:"notification,omitempty"`
  69. Subscriptions []*Subscription `json:"subscriptions,omitempty"`
  70. }
  71. // Tier represents a user's account type, including its account limits
  72. type Tier struct {
  73. ID string // Tier identifier (ti_...)
  74. Code string // Code of the tier
  75. Name string // Name of the tier
  76. MessageLimit int64 // Daily message limit
  77. MessageExpiryDuration time.Duration // Cache duration for messages
  78. EmailLimit int64 // Daily email limit
  79. CallLimit int64 // Daily phone call limit
  80. ReservationLimit int64 // Number of topic reservations allowed by user
  81. AttachmentFileSizeLimit int64 // Max file size per file (bytes)
  82. AttachmentTotalSizeLimit int64 // Total file size for all files of this user (bytes)
  83. AttachmentExpiryDuration time.Duration // Duration after which attachments will be deleted
  84. AttachmentBandwidthLimit int64 // Daily bandwidth limit for the user
  85. StripeMonthlyPriceID string // Monthly price ID for paid tiers (price_...)
  86. StripeYearlyPriceID string // Yearly price ID for paid tiers (price_...)
  87. }
  88. // Context returns fields for the log
  89. func (t *Tier) Context() log.Context {
  90. return log.Context{
  91. "tier_id": t.ID,
  92. "tier_code": t.Code,
  93. "stripe_monthly_price_id": t.StripeMonthlyPriceID,
  94. "stripe_yearly_price_id": t.StripeYearlyPriceID,
  95. }
  96. }
  97. // Subscription represents a user's topic subscription
  98. type Subscription struct {
  99. BaseURL string `json:"base_url"`
  100. Topic string `json:"topic"`
  101. DisplayName *string `json:"display_name"`
  102. }
  103. // Context returns fields for the log
  104. func (s *Subscription) Context() log.Context {
  105. return log.Context{
  106. "base_url": s.BaseURL,
  107. "topic": s.Topic,
  108. }
  109. }
  110. // NotificationPrefs represents the user's notification settings
  111. type NotificationPrefs struct {
  112. Sound *string `json:"sound,omitempty"`
  113. MinPriority *int `json:"min_priority,omitempty"`
  114. DeleteAfter *int `json:"delete_after,omitempty"`
  115. }
  116. // Stats is a struct holding daily user statistics
  117. type Stats struct {
  118. Messages int64
  119. Emails int64
  120. Calls int64
  121. }
  122. // Billing is a struct holding a user's billing information
  123. type Billing struct {
  124. StripeCustomerID string
  125. StripeSubscriptionID string
  126. StripeSubscriptionStatus stripe.SubscriptionStatus
  127. StripeSubscriptionInterval stripe.PriceRecurringInterval
  128. StripeSubscriptionPaidUntil time.Time
  129. StripeSubscriptionCancelAt time.Time
  130. }
  131. // Grant is a struct that represents an access control entry to a topic by a user
  132. type Grant struct {
  133. TopicPattern string // May include wildcard (*)
  134. Permission Permission
  135. Provisioned bool // Whether the grant was provisioned by the config file
  136. }
  137. // Reservation is a struct that represents the ownership over a topic by a user
  138. type Reservation struct {
  139. Topic string
  140. Owner Permission
  141. Everyone Permission
  142. }
  143. // Permission represents a read or write permission to a topic
  144. type Permission uint8
  145. // Permissions to a topic
  146. const (
  147. PermissionDenyAll Permission = iota
  148. PermissionRead
  149. PermissionWrite
  150. PermissionReadWrite // 3!
  151. )
  152. // NewPermission is a helper to create a Permission based on read/write bool values
  153. func NewPermission(read, write bool) Permission {
  154. p := uint8(0)
  155. if read {
  156. p |= uint8(PermissionRead)
  157. }
  158. if write {
  159. p |= uint8(PermissionWrite)
  160. }
  161. return Permission(p)
  162. }
  163. // ParsePermission parses the string representation and returns a Permission
  164. func ParsePermission(s string) (Permission, error) {
  165. switch strings.ToLower(s) {
  166. case "read-write", "rw":
  167. return NewPermission(true, true), nil
  168. case "read-only", "read", "ro":
  169. return NewPermission(true, false), nil
  170. case "write-only", "write", "wo":
  171. return NewPermission(false, true), nil
  172. case "deny-all", "deny", "none":
  173. return NewPermission(false, false), nil
  174. default:
  175. return NewPermission(false, false), errors.New("invalid permission")
  176. }
  177. }
  178. // IsRead returns true if readable
  179. func (p Permission) IsRead() bool {
  180. return p&PermissionRead != 0
  181. }
  182. // IsWrite returns true if writable
  183. func (p Permission) IsWrite() bool {
  184. return p&PermissionWrite != 0
  185. }
  186. // IsReadWrite returns true if readable and writable
  187. func (p Permission) IsReadWrite() bool {
  188. return p.IsRead() && p.IsWrite()
  189. }
  190. // String returns a string representation of the permission
  191. func (p Permission) String() string {
  192. if p.IsReadWrite() {
  193. return "read-write"
  194. } else if p.IsRead() {
  195. return "read-only"
  196. } else if p.IsWrite() {
  197. return "write-only"
  198. }
  199. return "deny-all"
  200. }
  201. // Role represents a user's role, either admin or regular user
  202. type Role string
  203. // User roles
  204. const (
  205. RoleAdmin = Role("admin") // Some queries have these values hardcoded!
  206. RoleUser = Role("user")
  207. RoleAnonymous = Role("anonymous")
  208. )
  209. // Everyone is a special username representing anonymous users
  210. const (
  211. Everyone = "*"
  212. everyoneID = "u_everyone"
  213. )
  214. // Error constants used by the package
  215. var (
  216. ErrUnauthenticated = errors.New("unauthenticated")
  217. ErrUnauthorized = errors.New("unauthorized")
  218. ErrInvalidArgument = errors.New("invalid argument")
  219. ErrUserNotFound = errors.New("user not found")
  220. ErrUserExists = errors.New("user already exists")
  221. ErrPasswordHashInvalid = errors.New("password hash but be a bcrypt hash, use 'ntfy user hash' to generate")
  222. ErrTierNotFound = errors.New("tier not found")
  223. ErrTokenNotFound = errors.New("token not found")
  224. ErrPhoneNumberNotFound = errors.New("phone number not found")
  225. ErrTooManyReservations = errors.New("new tier has lower reservation limit")
  226. ErrPhoneNumberExists = errors.New("phone number already exists")
  227. )