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.
154 lines
3.8 KiB
154 lines
3.8 KiB
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
|
|
}
|