types.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238
  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. }
  83. // Grant is a struct that represents an access control entry to a topic by a user
  84. type Grant struct {
  85. TopicPattern string // May include wildcard (*)
  86. Allow Permission
  87. }
  88. // Reservation is a struct that represents the ownership over a topic by a user
  89. type Reservation struct {
  90. Topic string
  91. Owner Permission
  92. Everyone Permission
  93. }
  94. // Permission represents a read or write permission to a topic
  95. type Permission uint8
  96. // Permissions to a topic
  97. const (
  98. PermissionDenyAll Permission = iota
  99. PermissionRead
  100. PermissionWrite
  101. PermissionReadWrite // 3!
  102. )
  103. // NewPermission is a helper to create a Permission based on read/write bool values
  104. func NewPermission(read, write bool) Permission {
  105. p := uint8(0)
  106. if read {
  107. p |= uint8(PermissionRead)
  108. }
  109. if write {
  110. p |= uint8(PermissionWrite)
  111. }
  112. return Permission(p)
  113. }
  114. // ParsePermission parses the string representation and returns a Permission
  115. func ParsePermission(s string) (Permission, error) {
  116. switch s {
  117. case "read-write", "rw":
  118. return NewPermission(true, true), nil
  119. case "read-only", "read", "ro":
  120. return NewPermission(true, false), nil
  121. case "write-only", "write", "wo":
  122. return NewPermission(false, true), nil
  123. case "deny-all", "deny", "none":
  124. return NewPermission(false, false), nil
  125. default:
  126. return NewPermission(false, false), errors.New("invalid permission")
  127. }
  128. }
  129. // IsRead returns true if readable
  130. func (p Permission) IsRead() bool {
  131. return p&PermissionRead != 0
  132. }
  133. // IsWrite returns true if writable
  134. func (p Permission) IsWrite() bool {
  135. return p&PermissionWrite != 0
  136. }
  137. // IsReadWrite returns true if readable and writable
  138. func (p Permission) IsReadWrite() bool {
  139. return p.IsRead() && p.IsWrite()
  140. }
  141. // String returns a string representation of the permission
  142. func (p Permission) String() string {
  143. if p.IsReadWrite() {
  144. return "read-write"
  145. } else if p.IsRead() {
  146. return "read-only"
  147. } else if p.IsWrite() {
  148. return "write-only"
  149. }
  150. return "deny-all"
  151. }
  152. // Role represents a user's role, either admin or regular user
  153. type Role string
  154. // User roles
  155. const (
  156. RoleAdmin = Role("admin") // Some queries have these values hardcoded!
  157. RoleUser = Role("user")
  158. RoleAnonymous = Role("anonymous")
  159. )
  160. // Everyone is a special username representing anonymous users
  161. const (
  162. Everyone = "*"
  163. )
  164. var (
  165. allowedUsernameRegex = regexp.MustCompile(`^[-_.@a-zA-Z0-9]+$`) // Does not include Everyone (*)
  166. allowedTopicRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`) // No '*'
  167. allowedTopicPatternRegex = regexp.MustCompile(`^[-_*A-Za-z0-9]{1,64}$`) // Adds '*' for wildcards!
  168. allowedTierRegex = regexp.MustCompile(`^[-_A-Za-z0-9]{1,64}$`)
  169. )
  170. // AllowedRole returns true if the given role can be used for new users
  171. func AllowedRole(role Role) bool {
  172. return role == RoleUser || role == RoleAdmin
  173. }
  174. // AllowedUsername returns true if the given username is valid
  175. func AllowedUsername(username string) bool {
  176. return allowedUsernameRegex.MatchString(username)
  177. }
  178. // AllowedTopic returns true if the given topic name is valid
  179. func AllowedTopic(topic string) bool {
  180. return allowedTopicRegex.MatchString(topic)
  181. }
  182. // AllowedTopicPattern returns true if the given topic pattern is valid; this includes the wildcard character (*)
  183. func AllowedTopicPattern(topic string) bool {
  184. return allowedTopicPatternRegex.MatchString(topic)
  185. }
  186. // AllowedTier returns true if the given tier name is valid
  187. func AllowedTier(tier string) bool {
  188. return allowedTierRegex.MatchString(tier)
  189. }
  190. // Error constants used by the package
  191. var (
  192. ErrUnauthenticated = errors.New("unauthenticated")
  193. ErrUnauthorized = errors.New("unauthorized")
  194. ErrInvalidArgument = errors.New("invalid argument")
  195. ErrUserNotFound = errors.New("user not found")
  196. ErrTierNotFound = errors.New("tier not found")
  197. )
  198. // BillingStatus represents the status of a Stripe subscription
  199. type BillingStatus string
  200. // BillingStatus values, subset of https://stripe.com/docs/billing/subscriptions/overview
  201. const (
  202. BillingStatusIncomplete = BillingStatus("incomplete")
  203. BillingStatusActive = BillingStatus("active")
  204. BillingStatusPastDue = BillingStatus("past_due")
  205. )