types.go 6.1 KB

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