package auth import ( "context" "errors" "fmt" "net/http" "strings" "time" 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") // A Token contains the parsed results from an bearer token. Its methods are safe to use with a nil receiver, but // the userID should be checked. type Token struct { UserID string Permissions []string } // Authenticated returns true if the token is non-nil and parsed func (token *Token) Authenticated() bool { return token != nil && token.UserID != "" } // Permitted returns true if the token is non-nil and has the given permission or the "admin" permission func (token *Token) Permitted(permissions ...string) bool { if token == nil { return false } for _, tokenPermission := range token.Permissions { if tokenPermission == "admin" { return true } for _, permission := range permissions { if permission == tokenPermission { return true } } } return false } // PermittedUser checks the first permission if the user matches, the second otherwise. This is a common // pattern. func (token *Token) PermittedUser(userID, permissionIfUser, permissionOtherwise string) bool { if token == nil { return false } if token.UserID == userID { return token.Permitted(permissionIfUser) } return token.Permitted(permissionOtherwise) } // TokenFromContext gets the token from context. func TokenFromContext(ctx context.Context) *Token { token, ok := ctx.Value(contextKey).(*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 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 Token{}, err } userid, permissions, err := parseClaims(jwtToken.Claims) if err != nil { return Token{}, err } if !key.ValidForUser(userid) { return Token{}, ErrWrongUser } user, err := FindUser(userid) if err != nil { return Token{}, ErrDeletedUser } for _, permission := range permissions { found := false for _, userPermission := range user.Permissions { if permission == userPermission { found = true break } } if !found { return Token{}, ErrWrongPermissions } } return Token{UserID: token.UserID, Permissions: permissions}, 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 }