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.

163 lines
4.3 KiB

  1. package auth
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "strings"
  8. "time"
  9. "git.aiterp.net/rpdata/api/models"
  10. "git.aiterp.net/rpdata/api/models/users"
  11. jwt "github.com/dgrijalva/jwt-go"
  12. )
  13. var contextKey = &struct{ data string }{"Token Context Key"}
  14. // ErrNoKid is returned if the key id is missing from the jwt token header,
  15. var ErrNoKid = errors.New("Missing \"kid\" field in token")
  16. // ErrKeyNotFound is returned if the key wasn't found.
  17. var ErrKeyNotFound = errors.New("Key not found")
  18. // ErrInvalidClaims is returned by parseClaims if the claims cannot be parsed
  19. var ErrInvalidClaims = errors.New("Invalid claims in token")
  20. // ErrExpired is returned by parseClaims if the expiry date is in the past
  21. var ErrExpired = errors.New("Claims have already expired")
  22. // ErrWrongUser is returned by CheckToken if the key cannot represent this user
  23. var ErrWrongUser = errors.New("Key is not valid for this user")
  24. // ErrWrongPermissions is returned by CheckToken if the key cannot claim one or more of its permissions
  25. var ErrWrongPermissions = errors.New("User does not have these permissions")
  26. // ErrDeletedUser is returned by CheckToken if the key can represent this user, but the user doesn't exist.
  27. var ErrDeletedUser = errors.New("User was not found")
  28. // ErrUnauthenticated is returned when the user is not authenticated
  29. var ErrUnauthenticated = errors.New("You are not authenticated")
  30. // ErrUnauthorized is returned when the user doesn't have access to this resource
  31. var ErrUnauthorized = errors.New("You are not authorized to perform this action")
  32. // ErrInvalidOperation is returned for operations that should never be allowed
  33. var ErrInvalidOperation = errors.New("No permission exists for this operation")
  34. // TokenFromContext gets the token from context.
  35. func TokenFromContext(ctx context.Context) *models.Token {
  36. token, ok := ctx.Value(contextKey).(*models.Token)
  37. if !ok {
  38. return nil
  39. }
  40. return token
  41. }
  42. // RequestWithToken either returns the request, or the request with a new context that
  43. // has the token.
  44. func RequestWithToken(r *http.Request) *http.Request {
  45. header := r.Header.Get("Authorization")
  46. if header == "" {
  47. return r
  48. }
  49. if !strings.HasPrefix(header, "Bearer ") {
  50. return r
  51. }
  52. token, err := CheckToken(header[7:])
  53. if err != nil {
  54. return r
  55. }
  56. return r.WithContext(context.WithValue(r.Context(), contextKey, &token))
  57. }
  58. // CheckToken reads the token string and returns a token if everything is kosher.
  59. func CheckToken(tokenString string) (token models.Token, err error) {
  60. var key Key
  61. jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) {
  62. if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok {
  63. return nil, fmt.Errorf("Unexpected signing method: %v", jwtToken.Header["alg"])
  64. }
  65. kid, ok := jwtToken.Header["kid"].(string)
  66. if !ok {
  67. return nil, ErrNoKid
  68. }
  69. key, err = FindKey(kid)
  70. if err != nil || key.ID == "" {
  71. return nil, ErrKeyNotFound
  72. }
  73. return []byte(key.Secret), nil
  74. })
  75. if err != nil {
  76. return models.Token{}, err
  77. }
  78. userid, permissions, err := parseClaims(jwtToken.Claims)
  79. if err != nil {
  80. return models.Token{}, err
  81. }
  82. if !key.ValidForUser(userid) {
  83. return models.Token{}, ErrWrongUser
  84. }
  85. user, err := users.Ensure(userid)
  86. if err != nil {
  87. return models.Token{}, ErrDeletedUser
  88. }
  89. acceptedPermissions := make([]string, 0, 8)
  90. for _, permission := range permissions {
  91. found := false
  92. for _, userPermission := range user.Permissions {
  93. if permission == userPermission {
  94. found = true
  95. break
  96. }
  97. }
  98. if found {
  99. acceptedPermissions = append(acceptedPermissions, permission)
  100. }
  101. }
  102. return models.Token{UserID: user.ID, Permissions: acceptedPermissions}, nil
  103. }
  104. func parseClaims(jwtClaims jwt.Claims) (userid string, permissions []string, err error) {
  105. mapClaims, ok := jwtClaims.(jwt.MapClaims)
  106. if !ok {
  107. return "", nil, ErrInvalidClaims
  108. }
  109. if !mapClaims.VerifyExpiresAt(time.Now().Unix(), true) {
  110. return "", nil, ErrExpired
  111. }
  112. if userid, ok = mapClaims["user"].(string); !ok {
  113. return "", nil, ErrInvalidClaims
  114. }
  115. if claimedPermissions, ok := mapClaims["permissions"].([]interface{}); ok {
  116. for _, permission := range claimedPermissions {
  117. if permission, ok := permission.(string); ok {
  118. permissions = append(permissions, permission)
  119. }
  120. }
  121. }
  122. if len(permissions) == 0 {
  123. return "", nil, ErrInvalidClaims
  124. }
  125. return
  126. }