Skip to content
Snippets Groups Projects
auth.go 5.83 KiB
Newer Older
  • Learn to ignore specific revisions
  • package api
    
    import (
    	"crypto/rand"
    	"crypto/subtle"
    	"encoding/json"
    	"log"
    	"net/http"
    	"time"
    
    	"github.com/dgrijalva/jwt-go"
    	"golang.org/x/crypto/argon2"
    )
    
    type creds struct {
    	Name     string `json:"name"`
    	Password string `json:"password"`
    
    meskio's avatar
    meskio committed
    	NoExpire bool   `json:"noExpire"`
    
    }
    
    func (a *api) SignIn(w http.ResponseWriter, req *http.Request) {
    	var c creds
    	err := json.NewDecoder(req.Body).Decode(&c)
    	if err != nil {
    		log.Printf("Can't decode auth credentials: %v", err)
    		w.WriteHeader(http.StatusInternalServerError)
    		return
    	}
    	var member Member
    	err = a.db.Where("name = ?", c.Name).First(&member).Error
    	if err != nil {
    		log.Printf("Can't locate user %s: %v", c.Name, err)
    		w.WriteHeader(http.StatusBadRequest)
    		return
    	}
    
    	hash := hashPass(c.Password, member.Salt)
    	if subtle.ConstantTimeCompare(hash, member.PassHash) == 0 {
    		log.Printf("Invalid pass for %s", c.Name)
    		w.WriteHeader(http.StatusBadRequest)
    		return
    	}
    
    	log.Printf("Logged in as %s", c.Name)
    	w.Header().Set("Content-Type", "application/json")
    	w.WriteHeader(http.StatusOK)
    
    
    meskio's avatar
    meskio committed
    	token, err := a.newToken(member.Num, member.Role, !c.NoExpire)
    
    	if err != nil {
    		log.Printf("Can't create a token: %v", err)
    		w.WriteHeader(http.StatusInternalServerError)
    		return
    	}
    	err = json.NewEncoder(w).Encode(map[string]interface{}{
    		"token":  token,
    		"member": member})
    	if err != nil {
    		log.Printf("Can't encode member: %v", err)
    		w.WriteHeader(http.StatusInternalServerError)
    	}
    }
    
    
    meskio's avatar
    meskio committed
    func (a *api) GetToken(w http.ResponseWriter, req *http.Request) {
    	token := req.Header.Get("x-authentication")
    	ok, claims := a.validateToken(token)
    	if !ok {
    		w.WriteHeader(http.StatusUnauthorized)
    		return
    	}
    	num, ok := claims["num"].(float64)
    	if !ok {
    		w.WriteHeader(http.StatusUnauthorized)
    		return
    	}
    	role, ok := claims["role"].(string)
    	if !ok {
    		w.WriteHeader(http.StatusUnauthorized)
    		return
    	}
    
    	w.Header().Set("Content-Type", "application/json")
    	w.WriteHeader(http.StatusOK)
    
    
    meskio's avatar
    meskio committed
    	token, err := a.newToken(int(num), role, true)
    
    meskio's avatar
    meskio committed
    	if err != nil {
    		log.Printf("Can't create a token: %v", err)
    		w.WriteHeader(http.StatusInternalServerError)
    		return
    	}
    	err = json.NewEncoder(w).Encode(map[string]interface{}{
    		"token": token,
    	})
    	if err != nil {
    		log.Printf("Can't encode token: %v", err)
    		w.WriteHeader(http.StatusInternalServerError)
    	}
    }
    
    
    func (a *api) auth(fn func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
    	return func(w http.ResponseWriter, req *http.Request) {
    		token := req.Header.Get("x-authentication")
    
    meskio's avatar
    meskio committed
    		if ok, _ := a.validateToken(token); !ok {
    
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		fn(w, req)
    	}
    }
    
    
    meskio's avatar
    meskio committed
    func (a *api) authNum(fn func(int, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
    	return func(w http.ResponseWriter, req *http.Request) {
    		token := req.Header.Get("x-authentication")
    		ok, claims := a.validateToken(token)
    		if !ok {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		num, ok := claims["num"].(float64)
    		if !ok {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		fn(int(num), w, req)
    	}
    }
    
    
    meskio's avatar
    meskio committed
    func (a *api) authAdmin(fn func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
    	return func(w http.ResponseWriter, req *http.Request) {
    		token := req.Header.Get("x-authentication")
    		ok, claims := a.validateToken(token)
    		if !ok {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		role, ok := claims["role"].(string)
    		if !ok || role != "admin" {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		fn(w, req)
    	}
    }
    
    
    meskio's avatar
    meskio committed
    func (a *api) authAdminNum(fn func(int, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
    	return func(w http.ResponseWriter, req *http.Request) {
    		token := req.Header.Get("x-authentication")
    		ok, claims := a.validateToken(token)
    		if !ok {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		role, ok := claims["role"].(string)
    		if !ok || role != "admin" {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		num, ok := claims["num"].(float64)
    		if !ok {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		fn(int(num), w, req)
    	}
    }
    
    
    meskio's avatar
    meskio committed
    func (a *api) authNumRole(fn func(int, string, http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
    	return func(w http.ResponseWriter, req *http.Request) {
    		token := req.Header.Get("x-authentication")
    		ok, claims := a.validateToken(token)
    		if !ok {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		num, ok := claims["num"].(float64)
    		if !ok {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		role, ok := claims["role"].(string)
    		if !ok {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		fn(int(num), role, w, req)
    	}
    }
    
    
    meskio's avatar
    meskio committed
    func (a *api) newToken(num int, role string, expire bool) (string, error) {
    	claims := jwt.MapClaims{
    
    		"num":  num,
    		"role": role,
    
    meskio's avatar
    meskio committed
    	}
    	if expire {
    		claims["exp"] = time.Now().Add(time.Hour * 24).Unix()
    	}
    
    	token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
    
    	return token.SignedString(a.signKey)
    }
    
    
    meskio's avatar
    meskio committed
    func (a *api) validateToken(token string) (bool, jwt.MapClaims) {
    
    	t, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
    		return a.signKey, nil
    	})
    	if err != nil {
    
    meskio's avatar
    meskio committed
    		return false, nil
    
    	}
    	if !t.Valid {
    
    meskio's avatar
    meskio committed
    		return false, nil
    
    	}
    	claims, ok := t.Claims.(jwt.MapClaims)
    	if !ok {
    
    meskio's avatar
    meskio committed
    		return false, nil
    
    meskio's avatar
    meskio committed
    	expClaim, ok := claims["exp"]
    	if !ok {
    		return true, claims
    	}
    	exp, ok := expClaim.(float64)
    
    	if !ok {
    
    meskio's avatar
    meskio committed
    		return false, claims
    
    meskio's avatar
    meskio committed
    	return time.Unix(int64(exp), 0).After(time.Now()), claims
    
    }
    
    func newHashPass(password string) (hash []byte, salt []byte, err error) {
    	salt = make([]byte, 32)
    	_, err = rand.Read(salt)
    	if err != nil {
    		return
    	}
    
    	hash = hashPass(password, salt)
    	return
    }
    
    func hashPass(password string, salt []byte) []byte {
    	const (
    		time    = 1
    		memory  = 64 * 1024
    		threads = 2
    		keyLen  = 32
    	)
    
    	return argon2.IDKey([]byte(password), salt, time, memory, threads, keyLen)
    }