182 lines
3.7 KiB
Go
182 lines
3.7 KiB
Go
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 {
|
|
// indicates whether authentication is enabled
|
|
Enabled() bool
|
|
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) Enabled() bool {
|
|
return a.opts.Username != "" && a.opts.Password != ""
|
|
}
|
|
|
|
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)
|
|
}
|