types.go 7.8 KB

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