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
186
auth/auth.go
186
auth/auth.go
|
@ -3,9 +3,13 @@ package auth
|
||||||
import (
|
import (
|
||||||
"crypto/aes"
|
"crypto/aes"
|
||||||
"crypto/cipher"
|
"crypto/cipher"
|
||||||
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -14,69 +18,168 @@ import (
|
||||||
"github.com/ishanjain28/pogo/common"
|
"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 {
|
func RequireAuthorization() common.Handler {
|
||||||
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
|
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
|
||||||
if usr := decryptSession(r); usr != nil {
|
usr, err := decryptCookie(r)
|
||||||
rc.User = usr
|
if err != nil {
|
||||||
return nil
|
fmt.Println(err.Error())
|
||||||
}
|
if strings.Contains(r.Header.Get("Accept"), "html") || r.Method == "GET" {
|
||||||
|
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
||||||
if strings.Contains(r.Header.Get("Accept"), "html") || r.Method == "GET" {
|
return &common.HTTPError{
|
||||||
http.Redirect(w, r, "/login", http.StatusTemporaryRedirect)
|
Message: "Unauthorized! Redirecting to /login",
|
||||||
return nil
|
StatusCode: http.StatusTemporaryRedirect,
|
||||||
} else {
|
}
|
||||||
|
}
|
||||||
return &common.HTTPError{
|
return &common.HTTPError{
|
||||||
Message: "Unauthorized!",
|
Message: "Unauthorized!",
|
||||||
StatusCode: http.StatusUnauthorized,
|
StatusCode: http.StatusUnauthorized,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
rc.User = usr
|
||||||
return &common.HTTPError{
|
return nil
|
||||||
Message: "Unauthorized!",
|
|
||||||
StatusCode: http.StatusUnauthorized,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateSession(u *common.User, w http.ResponseWriter) error {
|
func CreateSession(u *common.User) (*http.Cookie, error) {
|
||||||
|
|
||||||
// n_J6vaKjmmw4WB95DMorjQ.UMYdBLfttwPgQw9T0u0wdK7bGwDT9vwxoPAKWhjSAcpoiMsjh4eSfBkA4WB2deSoQu_cjCaJrcp77rvG67xkOeXsYpiclx2b-Oi7MHM3Kms.1507140277977.604800000.2CdxwiKAJT4SYJTVK-Du5jokr-CCnxo1ukdaVBkLRJg
|
|
||||||
|
|
||||||
iv, err := generateRandomString(16)
|
iv, err := generateRandomString(16)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
userJSON, err := json.Marshal(u)
|
userJSON, err := json.Marshal(u)
|
||||||
if err != nil {
|
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 {
|
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 != nil {
|
||||||
if err != http.ErrNoCookie {
|
if err != http.ErrNoCookie {
|
||||||
log.Printf("error in reading Cookie: %v", err)
|
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) {
|
func generateRandomString(l int) ([]byte, error) {
|
||||||
|
@ -88,3 +191,22 @@ func generateRandomString(l int) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return rBytes, nil
|
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()
|
// Authenticated endpoints should be passed to BasicAuth()
|
||||||
// first
|
// first
|
||||||
r.Handle("/admin", Handle(
|
r.Handle("/admin", Handle(
|
||||||
// auth.RequireAuthorization(),
|
auth.RequireAuthorization(),
|
||||||
adminHandler(),
|
adminHandler(),
|
||||||
))
|
)).Methods("GET", "POST")
|
||||||
|
|
||||||
r.Handle("/login", Handle(
|
r.Handle("/login", Handle(
|
||||||
loginHandler(),
|
loginHandler(),
|
||||||
))
|
)).Methods("GET", "POST")
|
||||||
|
|
||||||
// r.HandleFunc("/admin/publish", BasicAuth(CreateEpisode))
|
// r.HandleFunc("/admin/publish", BasicAuth(CreateEpisode))
|
||||||
// r.HandleFunc("/admin/delete", BasicAuth(RemoveEpisode))
|
// r.HandleFunc("/admin/delete", BasicAuth(RemoveEpisode))
|
||||||
|
@ -87,6 +87,11 @@ func Init() *mux.Router {
|
||||||
func loginHandler() common.Handler {
|
func loginHandler() common.Handler {
|
||||||
return func(rc *common.RouterContext, w http.ResponseWriter, r *http.Request) *common.HTTPError {
|
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" {
|
if r.Method == "GET" {
|
||||||
w.Header().Set("Content-Type", "text/html")
|
w.Header().Set("Content-Type", "text/html")
|
||||||
return common.ReadAndServeFile("assets/web/login.html", w)
|
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),
|
Message: fmt.Sprintf("error in reading users.json: %v", err),
|
||||||
StatusCode: http.StatusInternalServerError,
|
StatusCode: http.StatusInternalServerError,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = r.ParseForm()
|
err = r.ParseForm()
|
||||||
|
@ -127,15 +131,18 @@ func loginHandler() common.Handler {
|
||||||
for k, v := range u {
|
for k, v := range u {
|
||||||
if k == username && v == password {
|
if k == username && v == password {
|
||||||
// Create a cookie here because the credentials are correct
|
// Create a cookie here because the credentials are correct
|
||||||
err = auth.CreateSession(&common.User{
|
c, err := auth.CreateSession(&common.User{
|
||||||
Username: k,
|
Username: k,
|
||||||
}, w)
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &common.HTTPError{
|
return &common.HTTPError{
|
||||||
Message: err.Error(),
|
Message: err.Error(),
|
||||||
StatusCode: http.StatusInternalServerError,
|
StatusCode: http.StatusInternalServerError,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// r.AddCookie(c)
|
||||||
|
w.Header().Add("Set-Cookie", c.String())
|
||||||
// And now redirect the user to admin page
|
// And now redirect the user to admin page
|
||||||
http.Redirect(w, r, "/admin", http.StatusTemporaryRedirect)
|
http.Redirect(w, r, "/admin", http.StatusTemporaryRedirect)
|
||||||
return nil
|
return nil
|
||||||
|
|
Loading…
Reference in a new issue