Completed Cookie based sessions, Refactored and fixed bugs

This commit is contained in:
Ishan Jain 2017-10-05 11:03:57 +05:30
parent 5c138c7441
commit b9b825ec6b
2 changed files with 167 additions and 38 deletions

View file

@ -3,9 +3,13 @@ package auth
import (
"crypto/aes"
"crypto/cipher"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"log"
"net/http"
@ -14,69 +18,168 @@ import (
"github.com/ishanjain28/pogo/common"
)
const (
enc = "cookie_session_encryption"
// mac = "cookie_session_signature"
// This is the key with which each cookie is encrypted, I'll recommend moving it to a env file.
secret = "super_long_string_difficult_to_crack"
cookieName = "POGO_SESSION"
cookieExpiry = 60 * 60 * 24 * 30 // 30 days in seconds
)
func RequireAuthorization() common.Handler {
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
if usr := decryptSession(r); usr != nil {
rc.User = usr
return nil
}
if strings.Contains(r.Header.Get("Accept"), "html") || r.Method == "GET" {
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
return nil
} else {
usr, err := decryptCookie(r)
if err != nil {
fmt.Println(err.Error())
if strings.Contains(r.Header.Get("Accept"), "html") || r.Method == "GET" {
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
return &common.HTTPError{
Message: "Unauthorized! Redirecting to /login",
StatusCode: http.StatusTemporaryRedirect,
}
}
return &common.HTTPError{
Message: "Unauthorized!",
StatusCode: http.StatusUnauthorized,
}
}
return &common.HTTPError{
Message: "Unauthorized!",
StatusCode: http.StatusUnauthorized,
}
rc.User = usr
return nil
}
}
func CreateSession(u *common.User, w http.ResponseWriter) error {
// n_J6vaKjmmw4WB95DMorjQ.UMYdBLfttwPgQw9T0u0wdK7bGwDT9vwxoPAKWhjSAcpoiMsjh4eSfBkA4WB2deSoQu_cjCaJrcp77rvG67xkOeXsYpiclx2b-Oi7MHM3Kms.1507140277977.604800000.2CdxwiKAJT4SYJTVK-Du5jokr-CCnxo1ukdaVBkLRJg
func CreateSession(u *common.User) (*http.Cookie, error) {
iv, err := generateRandomString(16)
if err != nil {
return err
return nil, err
}
userJSON, err := json.Marshal(u)
if err != nil {
return err
return nil, err
}
var hexedJSON []byte
hex.Encode(hexedJSON, userJSON)
fmt.Println(iv, string(userJSON), hexedJSON)
hexedJSON := hex.EncodeToString(userJSON)
block, err := aes.NewCipher(hexedJSON)
encKey := deriveKey(enc, secret)
block, err := aes.NewCipher(encKey)
if err != nil {
return err
return nil, err
}
mode := cipher.NewCBCEncrypter(block, iv)
return nil
// Fill the block with 0x0e
if remBytes := len(hexedJSON) % aes.BlockSize; remBytes != 0 {
t := []byte(hexedJSON)
for i := 0; i < aes.BlockSize-remBytes; i++ {
t = append(t, 0x0e)
}
hexedJSON = string(t)
}
mode := cipher.NewCBCEncrypter(block, iv)
encCipher := make([]byte, len(hexedJSON)+aes.BlockSize)
mode.CryptBlocks(encCipher, []byte(hexedJSON))
cipherbase64 := base64urlencode(encCipher)
ivbase64 := base64urlencode(iv)
// Cookie format: iv.cipher.created_on.expire_on.HMAC
cookieStr := fmt.Sprintf("%s.%s", ivbase64, cipherbase64)
c := &http.Cookie{
Name: cookieName,
Value: cookieStr,
MaxAge: cookieExpiry,
}
return c, nil
}
func decryptSession(r *http.Request) *common.User {
func decryptCookie(r *http.Request) (*common.User, error) {
c, err := r.Cookie("POGO_SESSION")
c, err := r.Cookie(cookieName)
if err != nil {
if err != http.ErrNoCookie {
log.Printf("error in reading Cookie: %v", err)
}
return nil
return nil, err
}
fmt.Println(c)
return nil
csplit := strings.Split(c.Value, ".")
if len(csplit) != 2 {
return nil, errors.New("Invalid number of values in cookie")
}
ivb, cipherb := csplit[0], csplit[1]
iv, err := base64urldecode(ivb)
if err != nil {
return nil, err
}
dcipher, err := base64urldecode(cipherb)
if err != nil {
return nil, err
}
if len(iv) != 16 {
return nil, errors.New("IV length is not 16")
}
encKey := deriveKey(enc, secret)
if len(dcipher)%aes.BlockSize != 0 {
return nil, errors.New("ciphertext not multiple of blocksize")
}
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
buf := make([]byte, len(dcipher))
mode := cipher.NewCBCDecrypter(block, iv)
mode.CryptBlocks(buf, []byte(dcipher))
tstr := fmt.Sprintf("%x", buf)
// Remove aes padding, 0e is used because it was used in encryption to mark padding
padIndex := strings.Index(tstr, "0e")
if padIndex == -1 {
return nil, errors.New("Padding Index is -1")
}
tstr = tstr[:padIndex]
data, err := hex.DecodeString(tstr)
if err != nil {
return nil, err
}
data, err = hex.DecodeString(string(data))
if err != nil {
return nil, err
}
u := &common.User{}
err = json.Unmarshal(data, u)
if err != nil {
return nil, err
}
return u, nil
}
func deriveKey(msg, secret string) []byte {
key := []byte(secret)
sha256hash := hmac.New(sha256.New, key)
sha256hash.Write([]byte(msg))
return sha256hash.Sum(nil)
}
func generateRandomString(l int) ([]byte, error) {
@ -88,3 +191,22 @@ func generateRandomString(l int) ([]byte, error) {
}
return rBytes, nil
}
func base64urldecode(str string) ([]byte, error) {
base64str := strings.Replace(string(str), "-", "+", -1)
base64str = strings.Replace(base64str, "_", "/", -1)
s, err := base64.RawStdEncoding.DecodeString(base64str)
if err != nil {
return nil, err
}
return s, nil
}
func base64urlencode(str []byte) string {
base64str := strings.Replace(string(str), "+", "-", -1)
base64str = strings.Replace(base64str, "/", "_", -1)
return base64.RawStdEncoding.EncodeToString([]byte(base64str))
}

View file

@ -65,13 +65,13 @@ func Init() *mux.Router {
// Authenticated endpoints should be passed to BasicAuth()
// first
r.Handle("/admin", Handle(
// auth.RequireAuthorization(),
auth.RequireAuthorization(),
adminHandler(),
))
)).Methods("GET", "POST")
r.Handle("/login", Handle(
loginHandler(),
))
)).Methods("GET", "POST")
// r.HandleFunc("/admin/publish", BasicAuth(CreateEpisode))
// r.HandleFunc("/admin/delete", BasicAuth(RemoveEpisode))
@ -87,6 +87,11 @@ func Init() *mux.Router {
func loginHandler() common.Handler {
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
if _, err := r.Cookie("POGO_SESSION"); err == nil {
http.Redirect(w, r, "/admin", http.StatusTemporaryRedirect)
return nil
}
if r.Method == "GET" {
w.Header().Set("Content-Type", "text/html")
return common.ReadAndServeFile("assets/web/login.html", w)
@ -99,7 +104,6 @@ func loginHandler() common.Handler {
Message: fmt.Sprintf("error in reading users.json: %v", err),
StatusCode: http.StatusInternalServerError,
}
}
err = r.ParseForm()
@ -127,15 +131,18 @@ func loginHandler() common.Handler {
for k, v := range u {
if k == username && v == password {
// Create a cookie here because the credentials are correct
err = auth.CreateSession(&common.User{
c, err := auth.CreateSession(&common.User{
Username: k,
}, w)
})
if err != nil {
return &common.HTTPError{
Message: err.Error(),
StatusCode: http.StatusInternalServerError,
}
}
// r.AddCookie(c)
w.Header().Add("Set-Cookie", c.String())
// And now redirect the user to admin page
http.Redirect(w, r, "/admin", http.StatusTemporaryRedirect)
return nil