From cf3e84202b9c88b6aca3ef97b28b73b59885f9b8 Mon Sep 17 00:00:00 2001 From: Teajey <21069848+Teajey@users.noreply.github.com> Date: Sat, 4 May 2024 14:47:03 +1200 Subject: [PATCH] Initial --- .gitignore | 1 + auth/auth.go | 47 +++++++++++++++++++++++++++++ auth/login.go | 49 +++++++++++++++++++++++++++++++ auth/logout.go | 20 +++++++++++++ auth/register.go | 44 +++++++++++++++++++++++++++ context/context.go | 49 +++++++++++++++++++++++++++++++ context/foreign_wishlist.go | 27 +++++++++++++++++ context/home.go | 19 ++++++++++++ db/db.go | 40 +++++++++++++++++++++++++ db/user.go | 39 ++++++++++++++++++++++++ env/env.go | 3 ++ go.mod | 10 +++++++ go.sum | 8 +++++ main.go | 38 ++++++++++++++++++++++++ templates/base.gotmpl | 13 ++++++++ templates/foreign_wishlist.gotmpl | 16 ++++++++++ templates/home.gotmpl | 21 +++++++++++++ templates/login.go | 9 ++++++ templates/login.gotmpl | 14 +++++++++ templates/register.go | 9 ++++++ templates/register.gotmpl | 17 +++++++++++ templates/templates.go | 30 +++++++++++++++++++ types/user.go | 6 ++++ 23 files changed, 529 insertions(+) create mode 100644 .gitignore create mode 100644 auth/auth.go create mode 100644 auth/login.go create mode 100644 auth/logout.go create mode 100644 auth/register.go create mode 100644 context/context.go create mode 100644 context/foreign_wishlist.go create mode 100644 context/home.go create mode 100644 db/db.go create mode 100644 db/user.go create mode 100644 env/env.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 templates/base.gotmpl create mode 100644 templates/foreign_wishlist.gotmpl create mode 100644 templates/home.gotmpl create mode 100644 templates/login.go create mode 100644 templates/login.gotmpl create mode 100644 templates/register.go create mode 100644 templates/register.gotmpl create mode 100644 templates/templates.go create mode 100644 types/user.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fd0696 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +gin-bin diff --git a/auth/auth.go b/auth/auth.go new file mode 100644 index 0000000..3367812 --- /dev/null +++ b/auth/auth.go @@ -0,0 +1,47 @@ +package auth + +import ( + "log" + "net/http" + + "lishwist/db" + "lishwist/env" + "lishwist/types" + + "github.com/gorilla/sessions" +) + +type AuthMiddleware struct { + Store *sessions.CookieStore + protectedHandler http.Handler + publicHandler http.Handler +} + +func (auth *AuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) { + session, _ := auth.Store.Get(r, "lishwist_user") + authorized, _ := session.Values["authorized"].(bool) + if !authorized { + auth.publicHandler.ServeHTTP(w, r) + } else { + auth.protectedHandler.ServeHTTP(w, r) + } +} + +func (auth *AuthMiddleware) ExpectUser(r *http.Request) *types.UserData { + session, _ := auth.Store.Get(r, "lishwist_user") + username, ok := session.Values["username"].(string) + if !ok { + log.Fatalln("Failed to get username") + } + + user := db.GetUser(username) + if user == nil { + log.Fatalln("Failed to get user") + } + return user +} + +func NewAuthMiddleware(protectedHandler http.Handler, publicHandler http.Handler) *AuthMiddleware { + store := sessions.NewCookieStore([]byte(env.Secret)) + return &AuthMiddleware{store, protectedHandler, publicHandler} +} diff --git a/auth/login.go b/auth/login.go new file mode 100644 index 0000000..211466f --- /dev/null +++ b/auth/login.go @@ -0,0 +1,49 @@ +package auth + +import ( + "lishwist/db" + "lishwist/types" + "log" + "net/http" + "time" + + "golang.org/x/crypto/bcrypt" +) + +func (auth *AuthMiddleware) LoginPost(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + http.Error(w, "Couldn't parse form", http.StatusBadRequest) + return + } + + username := r.Form.Get("username") + password := r.Form.Get("password") + + user, ok := db.Get("user:" + username).(types.UserData) + if !ok { + time.Sleep(2 * time.Second) + http.Error(w, "Username or password invalid", http.StatusUnauthorized) + return + } + + err := bcrypt.CompareHashAndPassword(user.PassHash, []byte(password)) + if err != nil { + http.Error(w, "Username or password invalid", http.StatusUnauthorized) + return + } + + session, err := auth.Store.Get(r, "lishwist_user") + if err != nil { + http.Error(w, "Something went wrong. Error code: Sokka", http.StatusInternalServerError) + return + } + session.Values["authorized"] = true + session.Values["username"] = username + if err := session.Save(r, w); err != nil { + log.Println("Couldn't save session:", err) + http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError) + return + } + + http.Redirect(w, r, r.URL.Path, http.StatusSeeOther) +} diff --git a/auth/logout.go b/auth/logout.go new file mode 100644 index 0000000..ad0ef17 --- /dev/null +++ b/auth/logout.go @@ -0,0 +1,20 @@ +package auth + +import ( + "net/http" +) + +func (auth *AuthMiddleware) LogoutPost(w http.ResponseWriter, r *http.Request) { + session, err := auth.Store.Get(r, "lishwist_user") + if err != nil { + http.Error(w, "Something went wrong. Error code: Iroh", http.StatusInternalServerError) + return + } + session.Values = nil + if err := session.Save(r, w); err != nil { + http.Error(w, "Something went wrong. Error code: Azula", http.StatusInternalServerError) + return + } + + http.Redirect(w, r, "/", http.StatusSeeOther) +} diff --git a/auth/register.go b/auth/register.go new file mode 100644 index 0000000..30a5101 --- /dev/null +++ b/auth/register.go @@ -0,0 +1,44 @@ +package auth + +import ( + "net/http" + + "lishwist/db" + "lishwist/types" + + "golang.org/x/crypto/bcrypt" +) + +func (auth *AuthMiddleware) RegisterPost(w http.ResponseWriter, r *http.Request) { + if err := r.ParseForm(); err != nil { + http.Error(w, "Couldn't parse form", http.StatusBadRequest) + return + } + + username := r.Form.Get("username") + newPassword := r.Form.Get("newPassword") + confirmPassword := r.Form.Get("confirmPassword") + + if db.Exists("user:" + username) { + http.Error(w, "Username is taken", http.StatusBadRequest) + return + } + + if newPassword != confirmPassword { + http.Error(w, "passwords didn't match", http.StatusBadRequest) + return + } + + hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost) + if err != nil { + http.Error(w, "Something went wrong. Error code: Aang", http.StatusInternalServerError) + return + } + + db.Set("user:"+username, types.UserData{ + Username: username, + PassHash: hashedPasswordBytes, + }) + + http.Redirect(w, r, "/", http.StatusSeeOther) +} diff --git a/context/context.go b/context/context.go new file mode 100644 index 0000000..93f44d5 --- /dev/null +++ b/context/context.go @@ -0,0 +1,49 @@ +package context + +import ( + "lishwist/auth" + "lishwist/db" + "net/http" + "slices" +) + +type Context struct { + Auth *auth.AuthMiddleware +} + +func (ctx *Context) WishlistAdd(w http.ResponseWriter, r *http.Request) { + user := ctx.Auth.ExpectUser(r) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + items := db.GetUserItems(user.Username) + newItem := r.Form.Get("item") + if newItem != "" { + items = append(items, newItem) + } + db.SetUserItems(user.Username, items) + http.Redirect(w, r, "/", http.StatusSeeOther) +} + +func (ctx *Context) WishlistDelete(w http.ResponseWriter, r *http.Request) { + user := ctx.Auth.ExpectUser(r) + if err := r.ParseForm(); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + items := db.GetUserItems(user.Username) + target := r.Form.Get("item") + if target == "" { + http.Error(w, "Item not provided"+target, http.StatusBadRequest) + return + } + idx := slices.Index(items, target) + if idx < 0 { + http.Error(w, "Couldn't find item: "+target, http.StatusBadRequest) + return + } + items = append(items[:idx], items[idx+1:]...) + db.SetUserItems(user.Username, items) + http.Redirect(w, r, "/", http.StatusSeeOther) +} diff --git a/context/foreign_wishlist.go b/context/foreign_wishlist.go new file mode 100644 index 0000000..b11ad46 --- /dev/null +++ b/context/foreign_wishlist.go @@ -0,0 +1,27 @@ +package context + +import ( + "lishwist/db" + "lishwist/templates" + "net/http" +) + +type ForeignWishlistProps struct { + Username string + Items []string +} + +func (ctx *Context) ViewForeignWishlist(w http.ResponseWriter, r *http.Request) { + otherUsername := r.PathValue("username") + user := ctx.Auth.ExpectUser(r) + if user.Username == otherUsername { + http.Redirect(w, r, "/", http.StatusTemporaryRedirect) + return + } + items := db.GetUserItems(otherUsername) + if items == nil { + http.Error(w, "User not found", http.StatusNotFound) + } + p := ForeignWishlistProps{Username: otherUsername, Items: items} + templates.Execute(w, "foreign_wishlist.gotmpl", p) +} diff --git a/context/home.go b/context/home.go new file mode 100644 index 0000000..a9e4eed --- /dev/null +++ b/context/home.go @@ -0,0 +1,19 @@ +package context + +import ( + "net/http" + + "lishwist/db" + "lishwist/templates" +) + +type HomeProps struct { + Items []string +} + +func (ctx *Context) Home(w http.ResponseWriter, r *http.Request) { + user := ctx.Auth.ExpectUser(r) + items := db.GetUserItems(user.Username) + p := HomeProps{Items: items} + templates.Execute(w, "home.gotmpl", p) +} diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..f285e40 --- /dev/null +++ b/db/db.go @@ -0,0 +1,40 @@ +package db + +import "fmt" + +var database map[string]any = map[string]any{} + +func Add(key string, value any) error { + _, existing := database[key] + if existing { + return fmt.Errorf("A value already exists under '%s'", key) + } + database[key] = value + return nil +} + +func Set(key string, value any) { + database[key] = value +} + +func Get(key string) any { + value, existing := database[key] + if !existing { + return fmt.Errorf("No value under '%s'", key) + } + return value +} + +func Remove(key string) any { + value, existing := database[key] + if !existing { + return fmt.Errorf("No value under '%s'", key) + } + delete(database, key) + return value +} + +func Exists(key string) bool { + _, existing := database[key] + return existing +} diff --git a/db/user.go b/db/user.go new file mode 100644 index 0000000..0e05ea2 --- /dev/null +++ b/db/user.go @@ -0,0 +1,39 @@ +package db + +import ( + "fmt" + "lishwist/types" +) + +func GetUser(username string) *types.UserData { + user, ok := Get("user:" + username).(types.UserData) + if !ok { + return nil + } + return &user +} + +func GetUserItems(username string) []string { + user := GetUser(username) + if user == nil { + return nil + } + + items, ok := Get("user_items:" + user.Username).([]string) + if !ok { + return nil + } + + return items +} + +func SetUserItems(username string, items []string) error { + user := GetUser(username) + if user == nil { + return fmt.Errorf("Didn't find user") + } + + Set("user_items:"+user.Username, items) + + return nil +} diff --git a/env/env.go b/env/env.go new file mode 100644 index 0000000..ca24fce --- /dev/null +++ b/env/env.go @@ -0,0 +1,3 @@ +package env + +const Secret = "BLAHBLAHLBAH" diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1ab14a5 --- /dev/null +++ b/go.mod @@ -0,0 +1,10 @@ +module lishwist + +go 1.22.0 + +require ( + github.com/gorilla/sessions v1.2.2 + golang.org/x/crypto v0.22.0 +) + +require github.com/gorilla/securecookie v1.1.2 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..22712ff --- /dev/null +++ b/go.sum @@ -0,0 +1,8 @@ +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= +golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= diff --git a/main.go b/main.go new file mode 100644 index 0000000..1fd55c0 --- /dev/null +++ b/main.go @@ -0,0 +1,38 @@ +package main + +import ( + "net/http" + + "lishwist/auth" + "lishwist/context" + "lishwist/templates" +) + +func main() { + publicMux := http.NewServeMux() + protectedMux := http.NewServeMux() + + authMiddleware := auth.NewAuthMiddleware(protectedMux, publicMux) + + ctx := context.Context{ + Auth: authMiddleware, + } + + publicMux.HandleFunc("GET /register", templates.Register) + publicMux.HandleFunc("POST /register", authMiddleware.RegisterPost) + publicMux.HandleFunc("GET /", templates.Login) + publicMux.HandleFunc("POST /", authMiddleware.LoginPost) + + protectedMux.HandleFunc("GET /", ctx.Home) + protectedMux.HandleFunc("GET /{username}", ctx.ViewForeignWishlist) + protectedMux.HandleFunc("POST /wishlist/add", ctx.WishlistAdd) + protectedMux.HandleFunc("POST /wishlist/delete", ctx.WishlistDelete) + protectedMux.HandleFunc("POST /logout", authMiddleware.LogoutPost) + + // TODO: Remove me + protectedMux.HandleFunc("GET /logout", authMiddleware.LogoutPost) + + http.Handle("/", authMiddleware) + + http.ListenAndServe(":4000", nil) +} diff --git a/templates/base.gotmpl b/templates/base.gotmpl new file mode 100644 index 0000000..0a5642d --- /dev/null +++ b/templates/base.gotmpl @@ -0,0 +1,13 @@ + + + +
+