auth package
parent
768038161c
commit
113ac444a0
|
@ -0,0 +1,175 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var expirationDelta = time.Hour * 12
|
||||
|
||||
type AuthType int
|
||||
|
||||
const (
|
||||
AuthTypeUnknown AuthType = iota
|
||||
AuthTypeBasic
|
||||
AuthTypeToken
|
||||
)
|
||||
|
||||
type AuthRequest struct {
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
// JWT
|
||||
Token string
|
||||
AuthType AuthType `json:"-"`
|
||||
}
|
||||
|
||||
type AuthResponse struct {
|
||||
Token string `json:"token"`
|
||||
User User `json:"-"`
|
||||
}
|
||||
|
||||
type Authenticator interface {
|
||||
Authenticate(req *AuthRequest) (*AuthResponse, error)
|
||||
GenerateToken(u User) (*AuthResponse, error)
|
||||
}
|
||||
|
||||
func New(opts *Opts) *DefaultAuthenticator {
|
||||
if len(opts.Secret) == 0 {
|
||||
opts.Secret = []byte(randStringRunes(23))
|
||||
}
|
||||
|
||||
return &DefaultAuthenticator{
|
||||
opts: opts,
|
||||
secret: opts.Secret,
|
||||
}
|
||||
}
|
||||
|
||||
type Opts struct {
|
||||
// Basic auth
|
||||
Username string
|
||||
Password string
|
||||
|
||||
// Secret used to sign JWT tokens
|
||||
Secret []byte
|
||||
}
|
||||
|
||||
type DefaultAuthenticator struct {
|
||||
opts *Opts
|
||||
|
||||
secret []byte
|
||||
}
|
||||
|
||||
var (
|
||||
ErrUnauthorized = errors.New("unauthorized")
|
||||
)
|
||||
|
||||
func (a *DefaultAuthenticator) Authenticate(req *AuthRequest) (*AuthResponse, error) {
|
||||
|
||||
switch req.AuthType {
|
||||
case AuthTypeToken:
|
||||
user, err := a.parseToken(req.Token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a.GenerateToken(*user)
|
||||
case AuthTypeBasic:
|
||||
// ok
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown auth type")
|
||||
}
|
||||
|
||||
if a.opts.Username == "" && a.opts.Password == "" {
|
||||
// if basic auth not set - authenticating as guest
|
||||
return a.GenerateToken(User{Username: "guest"})
|
||||
}
|
||||
|
||||
if req.Username != a.opts.Username || req.Password != a.opts.Password {
|
||||
return nil, ErrUnauthorized
|
||||
}
|
||||
|
||||
return a.GenerateToken(User{Username: req.Username})
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Username string
|
||||
}
|
||||
|
||||
func (a *DefaultAuthenticator) GenerateToken(u User) (*AuthResponse, error) {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{
|
||||
"username": "admin",
|
||||
"exp": time.Now().Add(expirationDelta).Unix(),
|
||||
"iat": time.Now().Unix(),
|
||||
})
|
||||
|
||||
// Sign and get the complete encoded token as a string using the secret
|
||||
tokenString, err := token.SignedString(a.secret)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to sign token, error: %s, s: %s", err, string(a.secret))
|
||||
}
|
||||
|
||||
return &AuthResponse{
|
||||
Token: tokenString,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *DefaultAuthenticator) parseToken(tokenString string) (*User, error) {
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
return a.secret, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
|
||||
user := &User{}
|
||||
user.Username = parseString(claims, "username")
|
||||
if user.Username == "" {
|
||||
log.WithFields(log.Fields{
|
||||
"token": tokenString,
|
||||
"error": "token is missing account username field",
|
||||
}).Warn("authenticator: malformed token")
|
||||
return nil, fmt.Errorf("malformed token")
|
||||
}
|
||||
|
||||
// returning
|
||||
return user, nil
|
||||
|
||||
}
|
||||
return nil, fmt.Errorf("invalid token")
|
||||
|
||||
}
|
||||
|
||||
func parseString(meta map[string]interface{}, key string) string {
|
||||
val, ok := meta[key]
|
||||
if !ok {
|
||||
return ""
|
||||
}
|
||||
|
||||
s, ok := val.(string)
|
||||
if ok {
|
||||
return s
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
|
||||
func randStringRunes(n int) string {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
b := make([]rune, n)
|
||||
for i := range b {
|
||||
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
Loading…
Reference in New Issue