GraphQL API and utilities for the rpdata project
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

198 lines
4.8 KiB

  1. package auth
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "strings"
  8. "time"
  9. jwt "github.com/dgrijalva/jwt-go"
  10. )
  11. var contextKey = &struct{ data string }{"Token Context Key"}
  12. // ErrNoKid is returned if the key id is missing from the jwt token header,
  13. var ErrNoKid = errors.New("Missing \"kid\" field in token")
  14. // ErrKeyNotFound is returned if the key wasn't found.
  15. var ErrKeyNotFound = errors.New("Key not found")
  16. // ErrInvalidClaims is returned by parseClaims if the claims cannot be parsed
  17. var ErrInvalidClaims = errors.New("Invalid claims in token")
  18. // ErrExpired is returned by parseClaims if the expiry date is in the past
  19. var ErrExpired = errors.New("Claims have already expired")
  20. // ErrWrongUser is returned by CheckToken if the key cannot represent this user
  21. var ErrWrongUser = errors.New("Key is not valid for this user")
  22. // ErrWrongPermissions is returned by CheckToken if the key cannot claim one or more of its permissions
  23. var ErrWrongPermissions = errors.New("User does not have these permissions")
  24. // ErrDeletedUser is returned by CheckToken if the key can represent this user, but the user doesn't exist.
  25. var ErrDeletedUser = errors.New("User was not found")
  26. // A Token contains the parsed results from an bearer token. Its methods are safe to use with a nil receiver, but
  27. // the userID should be checked.
  28. type Token struct {
  29. UserID string
  30. Permissions []string
  31. }
  32. // Authenticated returns true if the token is non-nil and parsed
  33. func (token *Token) Authenticated() bool {
  34. return token != nil && token.UserID != ""
  35. }
  36. // Permitted returns true if the token is non-nil and has the given permission or the "admin" permission
  37. func (token *Token) Permitted(permissions ...string) bool {
  38. if token == nil {
  39. return false
  40. }
  41. for _, tokenPermission := range token.Permissions {
  42. if tokenPermission == "admin" {
  43. return true
  44. }
  45. for _, permission := range permissions {
  46. if permission == tokenPermission {
  47. return true
  48. }
  49. }
  50. }
  51. return false
  52. }
  53. // PermittedUser checks the first permission if the user matches, the second otherwise. This is a common
  54. // pattern.
  55. func (token *Token) PermittedUser(userID, permissionIfUser, permissionOtherwise string) bool {
  56. if token == nil {
  57. return false
  58. }
  59. if token.UserID == userID {
  60. return token.Permitted(permissionIfUser)
  61. }
  62. return token.Permitted(permissionOtherwise)
  63. }
  64. // TokenFromContext gets the token from context.
  65. func TokenFromContext(ctx context.Context) *Token {
  66. token, ok := ctx.Value(contextKey).(*Token)
  67. if !ok {
  68. return nil
  69. }
  70. return token
  71. }
  72. // RequestWithToken either returns the request, or the request with a new context that
  73. // has the token.
  74. func RequestWithToken(r *http.Request) *http.Request {
  75. header := r.Header.Get("Authorization")
  76. if header == "" {
  77. return r
  78. }
  79. if !strings.HasPrefix(header, "Bearer ") {
  80. return r
  81. }
  82. token, err := CheckToken(header[7:])
  83. if err != nil {
  84. return r
  85. }
  86. return r.WithContext(context.WithValue(r.Context(), contextKey, &token))
  87. }
  88. // CheckToken reads the token string and returns a token if everything is kosher.
  89. func CheckToken(tokenString string) (token Token, err error) {
  90. var key Key
  91. jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) {
  92. if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok {
  93. return nil, fmt.Errorf("Unexpected signing method: %v", jwtToken.Header["alg"])
  94. }
  95. kid, ok := jwtToken.Header["kid"].(string)
  96. if !ok {
  97. return nil, ErrNoKid
  98. }
  99. key, err = FindKey(kid)
  100. if err != nil || key.ID == "" {
  101. return nil, ErrKeyNotFound
  102. }
  103. return []byte(key.Secret), nil
  104. })
  105. if err != nil {
  106. return Token{}, err
  107. }
  108. userid, permissions, err := parseClaims(jwtToken.Claims)
  109. if err != nil {
  110. return Token{}, err
  111. }
  112. if !key.ValidForUser(userid) {
  113. return Token{}, ErrWrongUser
  114. }
  115. user, err := FindUser(userid)
  116. if err != nil {
  117. return Token{}, ErrDeletedUser
  118. }
  119. for _, permission := range permissions {
  120. found := false
  121. for _, userPermission := range user.Permissions {
  122. if permission == userPermission {
  123. found = true
  124. break
  125. }
  126. }
  127. if !found {
  128. return Token{}, ErrWrongPermissions
  129. }
  130. }
  131. return Token{UserID: token.UserID, Permissions: permissions}, nil
  132. }
  133. func parseClaims(jwtClaims jwt.Claims) (userid string, permissions []string, err error) {
  134. mapClaims, ok := jwtClaims.(jwt.MapClaims)
  135. if !ok {
  136. return "", nil, ErrInvalidClaims
  137. }
  138. if !mapClaims.VerifyExpiresAt(time.Now().Unix(), true) {
  139. return "", nil, ErrExpired
  140. }
  141. if userid, ok = mapClaims["user"].(string); !ok {
  142. return "", nil, ErrInvalidClaims
  143. }
  144. if claimedPermissions, ok := mapClaims["permissions"].([]interface{}); ok {
  145. for _, permission := range claimedPermissions {
  146. if permission, ok := permission.(string); ok {
  147. permissions = append(permissions, permission)
  148. }
  149. }
  150. }
  151. if len(permissions) == 0 {
  152. return "", nil, ErrInvalidClaims
  153. }
  154. return
  155. }