experiments

All kinds of coding experiments
Log | Files | Refs | Submodules

commit 0a24c08c3e23ef7f9a05529d9c7c72792e0618e1
parent b208717e2030b059d34df75c9abe88ec8950d230
Author: Vetle Haflan <vetle@haflan.dev>
Date:   Sat, 12 Feb 2022 17:13:48 +0100

wip: work on making redis-session into usable auth server

Diffstat:
Ago/redis-session/cmd/auth-server/main.go | 80+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ago/redis-session/cmd/tester/main.go | 44++++++++++++++++++++++++++++++++++++++++++++
Ago/redis-session/login.html | 21+++++++++++++++++++++
Dgo/redis-session/main.go | 5-----
Dgo/redis-session/redistest.go | 36------------------------------------
Dgo/redis-session/server.go | 56--------------------------------------------------------
Ago/redis-session/session/session.go | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
7 files changed, 211 insertions(+), 97 deletions(-)

diff --git a/go/redis-session/cmd/auth-server/main.go b/go/redis-session/cmd/auth-server/main.go @@ -0,0 +1,80 @@ +package main + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "io" + "net/http" + "os" + + "gl.haflan.dev/general/experiments/go/redis/session" +) + +var sessionIDSize = 32 + +func getRand() (string, error) { + sidBytes := make([]byte, sessionIDSize) + _, err := io.ReadFull(rand.Reader, sidBytes) + if err != nil { + return "", err + } + sidHex := make([]byte, hex.EncodedLen(len(sidBytes))) + hex.Encode(sidHex, sidBytes) + return string(sidHex), nil +} + +func postIndex(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + sid, err := getRand() + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + err = session.SetSession(sid, "me") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + return + } + c := &http.Cookie{ + Name: "sess_id", + Value: sid, + // Setting this includes subdomains + Domain: "local.test", + } + http.SetCookie(w, c) + gotoURL := r.URL.Query().Get("goto") + if gotoURL != "" { + fmt.Println(gotoURL) + http.Redirect(w, r, gotoURL, http.StatusPermanentRedirect) + } +} + +func getIndex(w http.ResponseWriter, r *http.Request) { + // TODO: If logged in, go directly to target page (or generic logged in page if no target) + loginPage, _ := os.ReadFile("login.html") + w.Write(loginPage) +} + +func handleIndex(w http.ResponseWriter, r *http.Request) { + fmt.Println(r.Method) + switch r.Method { + case http.MethodPost: + postIndex(w, r) + case http.MethodGet: + getIndex(w, r) + default: + w.WriteHeader(http.StatusMethodNotAllowed) + } +} + +func main() { + http.HandleFunc("/", handleIndex) + if err := http.ListenAndServe(":7676", nil); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/go/redis-session/cmd/tester/main.go b/go/redis-session/cmd/tester/main.go @@ -0,0 +1,44 @@ +package main + +import ( + "fmt" + "net/http" + "net/url" + "os" + + "gl.haflan.dev/general/experiments/go/redis/session" +) + +var authServer string + +func init() { + authServer = os.Getenv("AUTH_SERVER") + u, err := url.Parse(authServer) + if err != nil || u.Host == "" { + panic("malformed auth server url: " + authServer) + } +} + +func handleTest(w http.ResponseWriter, r *http.Request) { + gotoURL := authServer + "?goto=" + url.QueryEscape("http://"+r.Host+r.URL.Path) + c, err := r.Cookie("sess_id") + if err != nil { + http.Redirect(w, r, gotoURL, http.StatusTemporaryRedirect) + return + } + fmt.Println("Cookie is:", c.Value) + user, err := session.GetSession(c.Value) + if err != nil { + http.Redirect(w, r, gotoURL, http.StatusTemporaryRedirect) + return + } + w.Write([]byte("logged in as " + user)) +} + +func main() { + http.HandleFunc("/", handleTest) + if err := http.ListenAndServe(":7677", nil); err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/go/redis-session/login.html b/go/redis-session/login.html @@ -0,0 +1,20 @@ +<html> +<body> + <div><input type="text" id="username" placeholder="username"><br></div> + <div><input type="password" id="password" placeholder="password"></div> + <div><button onclick="doLogin()">Login</button></div> +</html> +<script type="text/javascript"> + function doLogin() { + let username = document.getElementById("username").value + let password = document.getElementById("password").value + let data = {username, password} + fetch('/' + location.search, { + method: 'POST', + mode: 'cors', + credentials: 'same-origin', + body: JSON.stringify(data) + }) + } +</script> +</body>+ \ No newline at end of file diff --git a/go/redis-session/main.go b/go/redis-session/main.go @@ -1,5 +0,0 @@ -package main - -func main() { - Serve() -} diff --git a/go/redis-session/redistest.go b/go/redis-session/redistest.go @@ -1,36 +0,0 @@ -package main - -import ( - "context" - "fmt" - "github.com/go-redis/redis/v8" - "os" - "time" -) - -var ctx = context.Background() - -func redistest() { - rdb := redis.NewClient(&redis.Options{ - Addr: "localhost:6379", - Password: "", - DB: 0, - }) - if len(os.Args) > 2 && os.Args[1] == "put" { - err := rdb.Set( - ctx, "sess:mkey", os.Args[2], time.Duration(5*time.Second), - ).Err() - if err != nil { - fmt.Println("Couldn't set key:", err) - } - return - } - val, err := rdb.Get(ctx, "sess:mkey").Result() - if err == redis.Nil { - fmt.Println("No such key") - } else if err != nil { - fmt.Println("Couldn't get key:", err) - } else { - fmt.Println(val) - } -} diff --git a/go/redis-session/server.go b/go/redis-session/server.go @@ -1,56 +0,0 @@ -package main - -import ( - "crypto/rand" - "encoding/hex" - "fmt" - "io" - "net/http" - "os" -) - -var sessionIDSize = 32 - -func getRand() (string, error) { - sidBytes := make([]byte, sessionIDSize) - _, err := io.ReadFull(rand.Reader, sidBytes) - if err != nil { - return "", err - } - sidHex := make([]byte, hex.EncodedLen(len(sidBytes))) - hex.Encode(sidHex, sidBytes) - return string(sidHex), nil -} - -func handleLogin(w http.ResponseWriter, r *http.Request) { - sid, err := getRand() - if err != nil { - w.WriteHeader(http.StatusInternalServerError) - return - } - c := &http.Cookie{ - Name: "sess_id", - Value: sid, - MaxAge: 120, - } - http.SetCookie(w, c) - return -} - -func handleTest(w http.ResponseWriter, r *http.Request) { - c, err := r.Cookie("sess_id") - if err != nil { - fmt.Println("Couldn't get cookie:", err) - } else { - fmt.Println(c.Value) - } -} - -func Serve() { - http.HandleFunc("/", handleTest) - http.HandleFunc("/login", handleLogin) - if err := http.ListenAndServe(":8888", nil); err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/go/redis-session/session/session.go b/go/redis-session/session/session.go @@ -0,0 +1,66 @@ +package session + +import ( + "context" + "errors" + "fmt" + "os" + "time" + + "github.com/go-redis/redis/v8" +) + +var rdb *redis.Client +var ctx = context.Background() +var expirationTime = time.Duration(15 * time.Second) + +func init() { + redisAddr := os.Getenv("REDIS_ADDR") + redisPW := os.Getenv("REDIS_PW") + if redisAddr == "" { + redisAddr = "localhost:6379" + } + rdb = redis.NewClient(&redis.Options{ + Addr: redisAddr, + Password: redisPW, + DB: 0, + }) + if rdb == nil { + panic("could not init Redis client") + } +} + +var ( + ErrNoSessID = errors.New("empty session id") + ErrInvalidSessID = errors.New("invalid session id") + ErrExpirationFailed = errors.New("unable to refresh session id expiration") +) + +func GetSession(sessID string) (data string, err error) { + if sessID == "" { + return "", ErrNoSessID + } + val, err := rdb.Get(ctx, "sess:"+sessID).Result() + if err == redis.Nil { + return "", ErrInvalidSessID + } else if err != nil { + return "", fmt.Errorf("could not read session id: %v", err) + } + // Keep session alive + _, err = rdb.Expire(ctx, "sess:"+sessID, expirationTime).Result() + if err != nil { + return "", ErrExpirationFailed + } + return val, err +} + +func SetSession(sessID, data string) error { + if sessID == "" { + return ErrNoSessID + } + err := rdb.Set(ctx, "sess:"+sessID, data, expirationTime).Err() + if err != nil { + return fmt.Errorf("failed to set session id: %v", err) + } + return nil +}