types.go 6.5 KB

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