Skip to content
Snippets Groups Projects
auth.go 8.81 KiB
Newer Older
  • Learn to ignore specific revisions
  • package api
    
    import (
    	"encoding/json"
    
    meskio's avatar
    meskio committed
    	"errors"
    
    	"log"
    	"net/http"
    	"time"
    
    
    meskio's avatar
    meskio committed
    	"0xacab.org/meskio/cicer/api/db"
    
    	"github.com/dgrijalva/jwt-go"
    
    meskio's avatar
    meskio committed
    	"github.com/gorilla/mux"
    
    )
    
    type creds struct {
    
    	Login    string `json:"login"`
    
    	Password string `json:"password"`
    
    meskio's avatar
    meskio committed
    	NoExpire bool   `json:"noExpire"`
    
    meskio's avatar
    meskio committed
    type passwordResetPost struct {
    	Email string `json:"email"`
    }
    
    type passwordResetPut struct {
    	Password string `json:"password"`
    
    meskio's avatar
    meskio committed
    	Login    string `json:"login"`
    
    meskio's avatar
    meskio committed
    }
    
    
    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
    	}
    
    
    meskio's avatar
    meskio committed
    	member, err := a.db.Login(c.Login, c.Password)
    	if err != nil {
    		log.Printf("Invalid pass for %s: %v", c.Login, err)
    
    		w.WriteHeader(http.StatusBadRequest)
    		return
    	}
    
    
    	log.Printf("Logged in as %s", c.Login)
    
    	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)
    	}
    }
    
    
    meskio's avatar
    meskio committed
    func (a *api) SendPasswordReset(w http.ResponseWriter, req *http.Request) {
    	var reset passwordResetPost
    	err := json.NewDecoder(req.Body).Decode(&reset)
    	if err != nil {
    		log.Printf("Can't decode password reset request: %v", err)
    		w.WriteHeader(http.StatusInternalServerError)
    		return
    	}
    
    meskio's avatar
    meskio committed
    	member, token, err := a.db.NewPasswordReset(reset.Email)
    
    meskio's avatar
    meskio committed
    	if err != nil {
    
    meskio's avatar
    meskio committed
    		if errors.Is(err, db.ErrorNotFound) {
    			w.WriteHeader(http.StatusBadRequest)
    			return
    		}
    		log.Printf("Error creating password reset: %v", err)
    
    meskio's avatar
    meskio committed
    		w.WriteHeader(http.StatusInternalServerError)
    		return
    	}
    
    meskio's avatar
    meskio committed
    	err = a.mail.sendPasswordReset(member, "/reset/"+token)
    
    meskio's avatar
    meskio committed
    	if err != nil {
    		log.Printf("Error sending password reset: %v", err)
    		w.WriteHeader(http.StatusInternalServerError)
    		return
    	}
    	w.WriteHeader(http.StatusCreated)
    
    meskio's avatar
    meskio committed
    	w.Write([]byte("Email sent"))
    
    meskio's avatar
    meskio committed
    }
    
    func (a *api) ValidatePasswordReset(w http.ResponseWriter, req *http.Request) {
    
    meskio's avatar
    meskio committed
    	vars := mux.Vars(req)
    	token := vars["token"]
    
    	passwordReset, err := a.db.GetPasswordReset(token)
    
    meskio's avatar
    meskio committed
    	if err != nil {
    		log.Printf("Can't get password reset %s: %v", token, err)
    		if errors.Is(err, db.ErrorNotFound) {
    			w.WriteHeader(http.StatusNotFound)
    		} else {
    			w.WriteHeader(http.StatusInternalServerError)
    		}
    
    meskio's avatar
    meskio committed
    		return
    	}
    
    
    	w.Header().Set("Content-Type", "application/json")
    
    meskio's avatar
    meskio committed
    	w.WriteHeader(http.StatusOK)
    
    	err = json.NewEncoder(w).Encode(*passwordReset.Member)
    	if err != nil {
    		log.Printf("Can't encode member: %v", err)
    		w.WriteHeader(http.StatusInternalServerError)
    		return
    	}
    
    meskio's avatar
    meskio committed
    }
    
    func (a *api) PasswordReset(w http.ResponseWriter, req *http.Request) {
    	var reset passwordResetPut
    	err := json.NewDecoder(req.Body).Decode(&reset)
    	if err != nil {
    		log.Printf("Can't decode password reset put: %v", err)
    		w.WriteHeader(http.StatusInternalServerError)
    		return
    	}
    
    	vars := mux.Vars(req)
    	token := vars["token"]
    
    meskio's avatar
    meskio committed
    	err = a.db.ResetPassword(token, reset.Password, reset.Login)
    
    meskio's avatar
    meskio committed
    	if err != nil {
    
    meskio's avatar
    meskio committed
    		log.Printf("Can't reset password %s: %v", token, err)
    		if errors.Is(err, db.ErrorNotFound) {
    			w.WriteHeader(http.StatusNotFound)
    
    meskio's avatar
    meskio committed
    		} else {
    
    meskio's avatar
    meskio committed
    			w.WriteHeader(http.StatusInternalServerError)
    
    meskio's avatar
    meskio committed
    		}
    
    meskio's avatar
    meskio committed
    		return
    
    meskio's avatar
    meskio committed
    	}
    
    meskio's avatar
    meskio committed
    	w.WriteHeader(http.StatusAccepted)
    
    meskio's avatar
    meskio committed
    	w.Write([]byte("Email sent"))
    
    meskio's avatar
    meskio committed
    }
    
    func (a *api) cleanPaswordResets() {
    	time.Sleep(time.Minute)
    	const refundSleeptime = 10 * time.Minute
    	for {
    		time.Sleep(refundSleeptime)
    
    meskio's avatar
    meskio committed
    		a.db.CleanPasswordReset()
    
    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 roleOrder(role string) bool {
    	return role == "admin" || role == "order"
    }
    
    func (a *api) authOrder(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 || !roleOrder(role) {
    			w.WriteHeader(http.StatusUnauthorized)
    			return
    		}
    		fn(w, req)
    	}
    }
    
    func (a *api) authOrderNum(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 || !roleOrder(role) {
    			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