1
0

types.go 7.7 KB

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