|
|
package auth
import ( "context" "errors" "fmt" "net/http" "strings" "time"
"git.aiterp.net/rpdata/api/models" "git.aiterp.net/rpdata/api/models/users" jwt "github.com/dgrijalva/jwt-go" )
var contextKey = &struct{ data string }{"Token Context Key"}
// ErrNoKid is returned if the key id is missing from the jwt token header,
var ErrNoKid = errors.New("Missing \"kid\" field in token")
// ErrKeyNotFound is returned if the key wasn't found.
var ErrKeyNotFound = errors.New("Key not found")
// ErrInvalidClaims is returned by parseClaims if the claims cannot be parsed
var ErrInvalidClaims = errors.New("Invalid claims in token")
// ErrExpired is returned by parseClaims if the expiry date is in the past
var ErrExpired = errors.New("Claims have already expired")
// ErrWrongUser is returned by CheckToken if the key cannot represent this user
var ErrWrongUser = errors.New("Key is not valid for this user")
// ErrWrongPermissions is returned by CheckToken if the key cannot claim one or more of its permissions
var ErrWrongPermissions = errors.New("User does not have these permissions")
// ErrDeletedUser is returned by CheckToken if the key can represent this user, but the user doesn't exist.
var ErrDeletedUser = errors.New("User was not found")
// TokenFromContext gets the token from context.
func TokenFromContext(ctx context.Context) *models.Token { token, ok := ctx.Value(contextKey).(*models.Token) if !ok { return nil }
return token }
// RequestWithToken either returns the request, or the request with a new context that
// has the token.
func RequestWithToken(r *http.Request) *http.Request { header := r.Header.Get("Authorization") if header == "" { return r }
if !strings.HasPrefix(header, "Bearer ") { return r }
token, err := CheckToken(header[7:]) if err != nil { return r }
return r.WithContext(context.WithValue(r.Context(), contextKey, &token)) }
// CheckToken reads the token string and returns a token if everything is kosher.
func CheckToken(tokenString string) (token models.Token, err error) { var key Key
jwtToken, err := jwt.Parse(tokenString, func(jwtToken *jwt.Token) (interface{}, error) { if _, ok := jwtToken.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", jwtToken.Header["alg"]) }
kid, ok := jwtToken.Header["kid"].(string) if !ok { return nil, ErrNoKid }
key, err = FindKey(kid) if err != nil || key.ID == "" { return nil, ErrKeyNotFound }
return []byte(key.Secret), nil }) if err != nil { return models.Token{}, err }
userid, permissions, err := parseClaims(jwtToken.Claims) if err != nil { return models.Token{}, err }
if !key.ValidForUser(userid) { return models.Token{}, ErrWrongUser }
user, err := users.Ensure(userid) if err != nil { return models.Token{}, ErrDeletedUser }
acceptedPermissions := make([]string, 0, 8) for _, permission := range permissions { found := false
for _, userPermission := range user.Permissions { if permission == userPermission { found = true break } }
if found { acceptedPermissions = append(acceptedPermissions, permission) } }
return models.Token{UserID: user.ID, Permissions: acceptedPermissions}, nil }
func parseClaims(jwtClaims jwt.Claims) (userid string, permissions []string, err error) { mapClaims, ok := jwtClaims.(jwt.MapClaims) if !ok { return "", nil, ErrInvalidClaims }
if !mapClaims.VerifyExpiresAt(time.Now().Unix(), true) { return "", nil, ErrExpired }
if userid, ok = mapClaims["user"].(string); !ok { return "", nil, ErrInvalidClaims }
if claimedPermissions, ok := mapClaims["permissions"].([]interface{}); ok { for _, permission := range claimedPermissions { if permission, ok := permission.(string); ok { permissions = append(permissions, permission) } } }
if len(permissions) == 0 { return "", nil, ErrInvalidClaims }
return }
|