github.com/bosssauce/ponzu@v0.11.1-0.20200102001432-9bc41b703131/system/admin/user/auth.go (about)

     1  // Package user contains the basic admin user creation and authentication code,
     2  // specific to Ponzu systems.
     3  package user
     4  
     5  import (
     6  	"bytes"
     7  	crand "crypto/rand"
     8  	"encoding/base64"
     9  	"log"
    10  	mrand "math/rand"
    11  	"net/http"
    12  	"time"
    13  
    14  	"github.com/nilslice/jwt"
    15  	"golang.org/x/crypto/bcrypt"
    16  )
    17  
    18  // User defines a admin user in the system
    19  type User struct {
    20  	ID    int    `json:"id"`
    21  	Email string `json:"email"`
    22  	Hash  string `json:"hash"`
    23  	Salt  string `json:"salt"`
    24  }
    25  
    26  var (
    27  	r = mrand.New(mrand.NewSource(time.Now().Unix()))
    28  )
    29  
    30  // New creates a user
    31  func New(email, password string) (*User, error) {
    32  	salt, err := randSalt()
    33  	if err != nil {
    34  		return nil, err
    35  	}
    36  
    37  	hash, err := hashPassword([]byte(password), salt)
    38  	if err != nil {
    39  		return nil, err
    40  	}
    41  
    42  	user := &User{
    43  		Email: email,
    44  		Hash:  string(hash),
    45  		Salt:  base64.StdEncoding.EncodeToString(salt),
    46  	}
    47  
    48  	return user, nil
    49  }
    50  
    51  // Auth is HTTP middleware to ensure the request has proper token credentials
    52  func Auth(next http.HandlerFunc) http.HandlerFunc {
    53  	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
    54  		redir := req.URL.Scheme + req.URL.Host + "/admin/login"
    55  
    56  		if IsValid(req) {
    57  			next.ServeHTTP(res, req)
    58  		} else {
    59  			http.Redirect(res, req, redir, http.StatusFound)
    60  		}
    61  	})
    62  }
    63  
    64  // IsValid checks if the user request is authenticated
    65  func IsValid(req *http.Request) bool {
    66  	// check if token exists in cookie
    67  	cookie, err := req.Cookie("_token")
    68  	if err != nil {
    69  		return false
    70  	}
    71  	// validate it and allow or redirect request
    72  	token := cookie.Value
    73  	return jwt.Passes(token)
    74  }
    75  
    76  // IsUser checks for consistency in email/pass combination
    77  func IsUser(usr *User, password string) bool {
    78  	salt, err := base64.StdEncoding.DecodeString(usr.Salt)
    79  	if err != nil {
    80  		return false
    81  	}
    82  
    83  	err = checkPassword([]byte(usr.Hash), []byte(password), salt)
    84  	if err != nil {
    85  		log.Println("Error checking password:", err)
    86  		return false
    87  	}
    88  
    89  	return true
    90  }
    91  
    92  // randSalt generates 16 * 8 bits of data for a random salt
    93  func randSalt() ([]byte, error) {
    94  	buf := make([]byte, 16)
    95  	count := len(buf)
    96  	n, err := crand.Read(buf)
    97  	if err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	if n != count || err != nil {
   102  		for count > 0 {
   103  			count--
   104  			buf[count] = byte(r.Int31n(256))
   105  		}
   106  	}
   107  
   108  	return buf, nil
   109  }
   110  
   111  // saltPassword combines the salt and password provided
   112  func saltPassword(password, salt []byte) ([]byte, error) {
   113  	salted := &bytes.Buffer{}
   114  	_, err := salted.Write(append(salt, password...))
   115  	if err != nil {
   116  		return nil, err
   117  	}
   118  
   119  	return salted.Bytes(), nil
   120  }
   121  
   122  // hashPassword encrypts the salted password using bcrypt
   123  func hashPassword(password, salt []byte) ([]byte, error) {
   124  	salted, err := saltPassword(password, salt)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  
   129  	hash, err := bcrypt.GenerateFromPassword(salted, 10)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  
   134  	return hash, nil
   135  }
   136  
   137  // checkPassword compares the hash with the salted password. A nil return means
   138  // the password is correct, but an error could mean either the password is not
   139  // correct, or the salt process failed - indicated in logs
   140  func checkPassword(hash, password, salt []byte) error {
   141  	salted, err := saltPassword(password, salt)
   142  	if err != nil {
   143  		return err
   144  	}
   145  
   146  	return bcrypt.CompareHashAndPassword(hash, salted)
   147  }