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