mirror of
https://github.com/gmemstr/pogo.git
synced 2024-09-20 01:31:09 +01:00
Completed Cookie based sessions, Refactored and fixed bugs
This commit is contained in:
parent
5c138c7441
commit
b9b825ec6b
190
auth/auth.go
190
auth/auth.go
|
@ -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 {
|
||||
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,
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return &common.HTTPError{
|
||||
Message: "Unauthorized!",
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
}
|
||||
}
|
||||
|
||||
return &common.HTTPError{
|
||||
Message: "Unauthorized!",
|
||||
StatusCode: http.StatusUnauthorized,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
return nil
|
||||
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,
|
||||
}
|
||||
|
||||
func decryptSession(r *http.Request) *common.User {
|
||||
return c, nil
|
||||
}
|
||||
|
||||
c, err := r.Cookie("POGO_SESSION")
|
||||
func decryptCookie(r *http.Request) (*common.User, error) {
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue