types.go 6.3 KB

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