From 782ffbbe6d715ad5703b429298abc1921e572f02 Mon Sep 17 00:00:00 2001 From: Teajey <21069848+Teajey@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:42:20 +0900 Subject: [PATCH] feat: register via json also lots of refactoring (sorry) --- server/api/register.go | 95 +++++++++++++++ server/auth/auth.go | 55 --------- server/auth/login_get.go | 62 ---------- server/auth/login_post.go | 58 --------- server/auth/register.go | 115 ------------------ server/db/db.go | 2 +- server/env/env.go | 2 +- server/main.go | 55 +++++---- server/router/router.go | 55 +++++++++ server/routing/context.go | 33 +++++ server/routing/error.go | 14 +++ .../{context => routing}/foreign_wishlist.go | 4 +- .../group_page.go => routing/groups.go} | 4 +- server/{context => routing}/home.go | 4 +- server/routing/json_params.go | 13 ++ server/routing/login.go | 115 ++++++++++++++++++ server/{auth => routing}/logout.go | 6 +- .../{auth => routing}/redirect_with_error.go | 6 +- server/routing/register.go | 70 +++++++++++ .../todo_update.go => routing/todo.go} | 4 +- .../context.go => routing/wishlist.go} | 13 +- server/templates/base.gotmpl | 2 +- server/templates/templates.go | 24 +++- 23 files changed, 469 insertions(+), 342 deletions(-) create mode 100644 server/api/register.go delete mode 100644 server/auth/auth.go delete mode 100644 server/auth/login_get.go delete mode 100644 server/auth/login_post.go delete mode 100644 server/auth/register.go create mode 100644 server/router/router.go create mode 100644 server/routing/context.go create mode 100644 server/routing/error.go rename server/{context => routing}/foreign_wishlist.go (97%) rename server/{context/group_page.go => routing/groups.go} (97%) rename server/{context => routing}/home.go (96%) create mode 100644 server/routing/json_params.go create mode 100644 server/routing/login.go rename server/{auth => routing}/logout.go (72%) rename server/{auth => routing}/redirect_with_error.go (61%) create mode 100644 server/routing/register.go rename server/{context/todo_update.go => routing/todo.go} (94%) rename server/{context/context.go => routing/wishlist.go} (92%) diff --git a/server/api/register.go b/server/api/register.go new file mode 100644 index 0000000..2c83c97 --- /dev/null +++ b/server/api/register.go @@ -0,0 +1,95 @@ +package api + +import ( + "log" + + "lishwist/db" + "lishwist/templates" + + "golang.org/x/crypto/bcrypt" +) + +type RegisterProps struct { + GeneralError string `json:",omitempty"` + Username templates.InputProps + Password templates.InputProps + ConfirmPassword templates.InputProps +} + +func (p *RegisterProps) Validate() (valid bool) { + if p.Password.Value != p.ConfirmPassword.Value { + p.ConfirmPassword.Error = "Passwords didn't match" + valid = false + } + + if !p.Username.Validate() { + valid = false + } + + if !p.Password.Validate() { + valid = false + } + + if !p.ConfirmPassword.Validate() { + valid = false + } + + return +} + +func NewRegisterProps(usernameVal, passwordVal, confirmPassVal string) *RegisterProps { + return &RegisterProps{ + GeneralError: "", + Username: templates.InputProps{ + Name: "username", + Required: true, + MinLength: 4, + Value: usernameVal, + }, + Password: templates.InputProps{ + Type: "password", + Name: "newPassword", + Required: true, + MinLength: 5, + Value: passwordVal, + }, + ConfirmPassword: templates.InputProps{ + Type: "password", + Name: "confirmPassword", + Required: true, + Value: confirmPassVal, + }, + } +} + +func Register(username, newPassword, confirmPassword string) *RegisterProps { + props := NewRegisterProps(username, newPassword, confirmPassword) + + valid := props.Validate() + props.Password.Value = "" + props.ConfirmPassword.Value = "" + if !valid { + return props + } + + existingUser, _ := db.GetUserByName(username) + if existingUser != nil { + props.Username.Error = "Username is taken" + return props + } + + hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost) + if err != nil { + props.GeneralError = "Something went wrong. Error code: Aang" + return props + } + + _, err = db.CreateUser(username, hashedPasswordBytes) + if err != nil { + log.Println("Registration error:", err) + props.GeneralError = "Something went wrong. Error code: Ozai" + return props + } + + return nil +} diff --git a/server/auth/auth.go b/server/auth/auth.go deleted file mode 100644 index d2ee3eb..0000000 --- a/server/auth/auth.go +++ /dev/null @@ -1,55 +0,0 @@ -package auth - -import ( - "encoding/gob" - "log" - "net/http" - - "lishwist/db" - "lishwist/env" - - "github.com/Teajey/sqlstore" -) - -type AuthMiddleware struct { - Store *sqlstore.Store - 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) *db.User { - session, _ := auth.Store.Get(r, "lishwist_user") - username, ok := session.Values["username"].(string) - if !ok { - log.Fatalln("Failed to get username") - } - - user, err := db.GetUserByName(username) - if err != nil { - log.Fatalf("Failed to get user: %s\n", err) - } - return user -} - -func NewAuthMiddleware(protectedHandler http.Handler, publicHandler http.Handler) *AuthMiddleware { - gob.Register(&RegisterProps{}) - gob.Register(&LoginProps{}) - store, err := db.NewSessionStore() - if err != nil { - log.Fatalln("Failed to create store:", err) - } - store.Options.MaxAge = 86_400 - store.Options.Secure = !env.InDev - store.Options.HttpOnly = true - return &AuthMiddleware{store, protectedHandler, publicHandler} -} diff --git a/server/auth/login_get.go b/server/auth/login_get.go deleted file mode 100644 index 1627bac..0000000 --- a/server/auth/login_get.go +++ /dev/null @@ -1,62 +0,0 @@ -package auth - -import ( - sesh "lishwist/session" - "lishwist/templates" - "net/http" -) - -type LoginProps struct { - GeneralError string - SuccessfulRegistration bool - Username templates.InputProps - Password templates.InputProps -} - -func NewLoginProps() LoginProps { - return LoginProps{ - Username: templates.InputProps{ - Name: "username", - Required: true, - }, - Password: templates.InputProps{ - Name: "password", - Type: "password", - Required: true, - }, - } -} - -func (auth *AuthMiddleware) Login(w http.ResponseWriter, r *http.Request) { - session, _ := auth.Store.Get(r, "lishwist_user") - - props := NewLoginProps() - - flash, err := sesh.GetFirstFlash(w, r, session, "login_props") - if err != nil { - http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError) - return - } - - flashProps, ok := flash.(*LoginProps) - if ok { - props.Username.Value = flashProps.Username.Value - - props.GeneralError = flashProps.GeneralError - props.Username.Error = flashProps.Username.Error - props.Password.Error = flashProps.Password.Error - } - - flash, err = sesh.GetFirstFlash(w, r, session, "successful_registration") - if err != nil { - http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError) - return - } - - successfulReg, _ := flash.(bool) - if successfulReg { - props.SuccessfulRegistration = true - } - - templates.Execute(w, "login.gotmpl", props) -} diff --git a/server/auth/login_post.go b/server/auth/login_post.go deleted file mode 100644 index 9d19a67..0000000 --- a/server/auth/login_post.go +++ /dev/null @@ -1,58 +0,0 @@ -package auth - -import ( - "lishwist/db" - "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") - - props := NewLoginProps() - props.Username.Value = username - - user, err := db.GetUserByName(username) - if user == nil || err != nil { - time.Sleep(time.Second) - props.GeneralError = "Username or password invalid" - auth.RedirectWithFlash(w, r, "/", "login_props", &props) - return - } - - passHash, err := user.GetPassHash() - if err != nil { - props.GeneralError = "Something went wrong. Error code: Momo" - auth.RedirectWithFlash(w, r, "/", "login_props", &props) - return - } - - err = bcrypt.CompareHashAndPassword(passHash, []byte(password)) - if err != nil { - props.GeneralError = "Username or password invalid" - auth.RedirectWithFlash(w, r, "/", "login_props", &props) - return - } - - // NOTE: Overwriting any existing cookie or session here. So we don't care if there's an error - session, _ := auth.Store.Get(r, "lishwist_user") - session.ID = "" - 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/server/auth/register.go b/server/auth/register.go deleted file mode 100644 index 9398f2b..0000000 --- a/server/auth/register.go +++ /dev/null @@ -1,115 +0,0 @@ -package auth - -import ( - "log" - "net/http" - - "lishwist/db" - "lishwist/templates" - - "golang.org/x/crypto/bcrypt" -) - -type RegisterProps struct { - GeneralError string - Username templates.InputProps - Password templates.InputProps - ConfirmPassword templates.InputProps -} - -func NewRegisterProps() RegisterProps { - return RegisterProps{ - GeneralError: "", - Username: templates.InputProps{ - Name: "username", - Required: true, - }, - Password: templates.InputProps{ - Type: "password", - Name: "newPassword", - Required: true, - MinLength: 5, - }, - ConfirmPassword: templates.InputProps{ - Type: "password", - Name: "confirmPassword", - Required: true, - }, - } -} - -func (auth *AuthMiddleware) Register(w http.ResponseWriter, r *http.Request) { - props := NewRegisterProps() - - session, _ := auth.Store.Get(r, "lishwist_user") - if flashes := session.Flashes("register_props"); len(flashes) > 0 { - flashProps, _ := flashes[0].(*RegisterProps) - props.Username.Value = flashProps.Username.Value - - props.GeneralError = flashProps.GeneralError - props.Username.Error = flashProps.Username.Error - props.ConfirmPassword.Error = flashProps.ConfirmPassword.Error - } - - 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 - } - - templates.Execute(w, "register.gotmpl", props) -} - -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") - - props := NewRegisterProps() - props.Username.Value = username - props.Password.Value = newPassword - props.ConfirmPassword.Value = confirmPassword - - existingUser, _ := db.GetUserByName(username) - if existingUser != nil { - props.Username.Error = "Username is taken" - auth.RedirectWithFlash(w, r, "/register", "register_props", &props) - return - } - - if newPassword != confirmPassword { - props.ConfirmPassword.Error = "Password didn't match" - auth.RedirectWithFlash(w, r, "/register", "register_props", &props) - return - } - - hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost) - if err != nil { - props.GeneralError = "Something went wrong. Error code: Aang" - auth.RedirectWithFlash(w, r, "/register", "register_props", &props) - return - } - - _, err = db.CreateUser(username, hashedPasswordBytes) - if err != nil { - log.Println("Registration error:", err) - props.GeneralError = "Something went wrong. Error code: Ozai" - auth.RedirectWithFlash(w, r, "/register", "register_props", &props) - return - } - - session, _ := auth.Store.Get(r, "lishwist_user") - session.AddFlash(true, "successful_registration") - 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, "/", http.StatusSeeOther) -} diff --git a/server/db/db.go b/server/db/db.go index 109bfb5..eff20d8 100644 --- a/server/db/db.go +++ b/server/db/db.go @@ -56,7 +56,7 @@ func NewSessionStore() (*sqlstore.Store, error) { Insert: insertStmt, Select: selectStmt, Update: updateStmt, - }, []byte(env.JwtSecret)) + }, []byte(env.SessionSecret)) return s, nil } diff --git a/server/env/env.go b/server/env/env.go index 3f9f00b..a6f27be 100644 --- a/server/env/env.go +++ b/server/env/env.go @@ -14,7 +14,7 @@ func GuaranteeEnv(key string) (variable string) { return } -var JwtSecret = GuaranteeEnv("LISHWIST_SESSION_SECRET") +var SessionSecret = GuaranteeEnv("LISHWIST_SESSION_SECRET") var HostRootUrl = GuaranteeEnv("LISHWIST_HOST_ROOT_URL") var HostPort = os.Getenv("LISHWIST_HOST_PORT") var ServePort = GuaranteeEnv("LISHWIST_SERVE_PORT") diff --git a/server/main.go b/server/main.go index 8ab54f4..d316b4f 100644 --- a/server/main.go +++ b/server/main.go @@ -1,16 +1,21 @@ package main import ( + "encoding/gob" "log" "net/http" - "lishwist/auth" - "lishwist/context" + "lishwist/api" "lishwist/db" "lishwist/env" + "lishwist/router" + "lishwist/routing" ) func main() { + gob.Register(&api.RegisterProps{}) + gob.Register(&routing.LoginProps{}) + err := db.Open() if err != nil { log.Fatalf("Failed to open DB: %s\n", err) @@ -20,31 +25,37 @@ func main() { log.Fatalf("Failed to init DB: %s\n", err) } - publicMux := http.NewServeMux() - protectedMux := http.NewServeMux() - - authMiddleware := auth.NewAuthMiddleware(protectedMux, publicMux) - - ctx := context.Context{ - Auth: authMiddleware, + store, err := db.NewSessionStore() + if err != nil { + log.Fatalf("Failed to ") } + store.Options.MaxAge = 86_400 + store.Options.Secure = !env.InDev + store.Options.HttpOnly = true - publicMux.HandleFunc("GET /register", authMiddleware.Register) - publicMux.HandleFunc("POST /register", authMiddleware.RegisterPost) - publicMux.HandleFunc("GET /", authMiddleware.Login) - publicMux.HandleFunc("POST /", authMiddleware.LoginPost) - publicMux.HandleFunc("GET /list/{userReference}", ctx.PublicWishlist) - publicMux.HandleFunc("GET /group/{groupReference}", ctx.PublicGroupPage) + r := router.New(store) - protectedMux.HandleFunc("GET /{$}", ctx.Home) - protectedMux.HandleFunc("POST /{$}", ctx.HomePost) - protectedMux.HandleFunc("GET /list/{userReference}", ctx.ForeignWishlist) - protectedMux.HandleFunc("POST /list/{userReference}", ctx.ForeignWishlistPost) - protectedMux.HandleFunc("GET /group/{groupReference}", ctx.GroupPage) - protectedMux.HandleFunc("POST /logout", authMiddleware.LogoutPost) + route := routing.NewContext(store) - http.Handle("/", authMiddleware) + r.Html.Public.HandleFunc("GET /register", route.Register) + r.Html.Public.HandleFunc("POST /register", route.RegisterPost) + r.Html.Public.HandleFunc("GET /", route.Login) + r.Html.Public.HandleFunc("POST /", route.LoginPost) + r.Html.Public.HandleFunc("GET /list/{userReference}", route.PublicWishlist) + r.Html.Public.HandleFunc("GET /group/{groupReference}", route.PublicGroupPage) + r.Html.Private.HandleFunc("GET /{$}", route.Home) + r.Html.Private.HandleFunc("POST /{$}", route.HomePost) + r.Html.Private.HandleFunc("GET /list/{userReference}", route.ForeignWishlist) + r.Html.Private.HandleFunc("POST /list/{userReference}", route.ForeignWishlistPost) + r.Html.Private.HandleFunc("GET /group/{groupReference}", route.GroupPage) + r.Html.Private.HandleFunc("POST /logout", route.LogoutPost) + + r.Json.Public.HandleFunc("POST /register", route.RegisterPostJson) + + http.Handle("/", r) + + log.Printf("Running at http://127.0.0.1:%s\n", env.ServePort) err = http.ListenAndServe(":"+env.ServePort, nil) if err != nil { log.Fatalln("Failed to listen and server:", err) diff --git a/server/router/router.go b/server/router/router.go new file mode 100644 index 0000000..27e5fe0 --- /dev/null +++ b/server/router/router.go @@ -0,0 +1,55 @@ +package router + +import ( + "net/http" + + "github.com/Teajey/sqlstore" +) + +type VisibilityRouter struct { + Store *sqlstore.Store + Public *http.ServeMux + Private *http.ServeMux +} + +func (s *VisibilityRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) { + session, _ := s.Store.Get(r, "lishwist_user") + authorized, _ := session.Values["authorized"].(bool) + + if authorized { + s.Private.ServeHTTP(w, r) + } else { + s.Public.ServeHTTP(w, r) + } +} + +type Router struct { + Json VisibilityRouter + Html VisibilityRouter +} + +func (s *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { + contentType := r.Header.Get("Content-Type") + + switch contentType { + case "application/json": + s.Json.ServeHTTP(w, r) + default: + s.Html.ServeHTTP(w, r) + } +} + +func New(store *sqlstore.Store) *Router { + return &Router{ + Json: VisibilityRouter{ + Store: store, + Public: http.NewServeMux(), + Private: http.NewServeMux(), + }, + Html: VisibilityRouter{ + Store: store, + Public: http.NewServeMux(), + Private: http.NewServeMux(), + }, + } +} diff --git a/server/routing/context.go b/server/routing/context.go new file mode 100644 index 0000000..6480b81 --- /dev/null +++ b/server/routing/context.go @@ -0,0 +1,33 @@ +package routing + +import ( + "lishwist/db" + "log" + "net/http" + + "github.com/Teajey/sqlstore" +) + +type Context struct { + store *sqlstore.Store +} + +func NewContext(store *sqlstore.Store) *Context { + return &Context{ + store, + } +} + +func (auth *Context) ExpectUser(r *http.Request) *db.User { + session, _ := auth.store.Get(r, "lishwist_user") + username, ok := session.Values["username"].(string) + if !ok { + log.Fatalln("Failed to get username") + } + + user, err := db.GetUserByName(username) + if err != nil { + log.Fatalf("Failed to get user: %s\n", err) + } + return user +} diff --git a/server/routing/error.go b/server/routing/error.go new file mode 100644 index 0000000..79d1f65 --- /dev/null +++ b/server/routing/error.go @@ -0,0 +1,14 @@ +package routing + +import ( + "fmt" + "net/http" + "strings" +) + +func writeGeneralError(w http.ResponseWriter, msg string, status int) { + w.WriteHeader(status) + escapedMsg := strings.ReplaceAll(msg, `"`, `\"`) + _, _ = w.Write([]byte(fmt.Sprintf(`{"GeneralError":"%s"}`, escapedMsg))) + w.Header().Add("Content-Type", "application/json") +} diff --git a/server/context/foreign_wishlist.go b/server/routing/foreign_wishlist.go similarity index 97% rename from server/context/foreign_wishlist.go rename to server/routing/foreign_wishlist.go index ce8165c..d974125 100644 --- a/server/context/foreign_wishlist.go +++ b/server/routing/foreign_wishlist.go @@ -1,4 +1,4 @@ -package context +package routing import ( "lishwist/db" @@ -16,7 +16,7 @@ type foreignWishlistProps struct { func (ctx *Context) ForeignWishlist(w http.ResponseWriter, r *http.Request) { userReference := r.PathValue("userReference") - user := ctx.Auth.ExpectUser(r) + user := ctx.ExpectUser(r) if user.Reference == userReference { http.Redirect(w, r, "/", http.StatusSeeOther) return diff --git a/server/context/group_page.go b/server/routing/groups.go similarity index 97% rename from server/context/group_page.go rename to server/routing/groups.go index d4beb22..1573c7b 100644 --- a/server/context/group_page.go +++ b/server/routing/groups.go @@ -1,4 +1,4 @@ -package context +package routing import ( "net/http" @@ -15,7 +15,7 @@ type GroupProps struct { } func (ctx *Context) GroupPage(w http.ResponseWriter, r *http.Request) { - user := ctx.Auth.ExpectUser(r) + user := ctx.ExpectUser(r) groupReference := r.PathValue("groupReference") group, err := user.GetGroupByReference(groupReference) if err != nil { diff --git a/server/context/home.go b/server/routing/home.go similarity index 96% rename from server/context/home.go rename to server/routing/home.go index 1fb15b2..18ac4a4 100644 --- a/server/context/home.go +++ b/server/routing/home.go @@ -1,4 +1,4 @@ -package context +package routing import ( "net/http" @@ -19,7 +19,7 @@ type HomeProps struct { } func (ctx *Context) Home(w http.ResponseWriter, r *http.Request) { - user := ctx.Auth.ExpectUser(r) + user := ctx.ExpectUser(r) gifts, err := user.GetGifts() if err != nil { error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err) diff --git a/server/routing/json_params.go b/server/routing/json_params.go new file mode 100644 index 0000000..b550c51 --- /dev/null +++ b/server/routing/json_params.go @@ -0,0 +1,13 @@ +package routing + +import ( + "encoding/json" + "net/http" +) + +func decodeJsonParams(r *http.Request, v any) error { + dec := json.NewDecoder(r.Body) + dec.DisallowUnknownFields() + err := dec.Decode(&v) + return err +} diff --git a/server/routing/login.go b/server/routing/login.go new file mode 100644 index 0000000..1fd9135 --- /dev/null +++ b/server/routing/login.go @@ -0,0 +1,115 @@ +package routing + +import ( + "lishwist/db" + sesh "lishwist/session" + "lishwist/templates" + "log" + "net/http" + "time" + + "golang.org/x/crypto/bcrypt" +) + +type LoginProps struct { + GeneralError string + SuccessfulRegistration bool + Username templates.InputProps + Password templates.InputProps +} + +func NewLoginProps() LoginProps { + return LoginProps{ + Username: templates.InputProps{ + Name: "username", + Required: true, + }, + Password: templates.InputProps{ + Name: "password", + Type: "password", + Required: true, + }, + } +} + +func (ctx *Context) Login(w http.ResponseWriter, r *http.Request) { + session, _ := ctx.store.Get(r, "lishwist_user") + + props := NewLoginProps() + + flash, err := sesh.GetFirstFlash(w, r, session, "login_props") + if err != nil { + http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError) + return + } + + flashProps, ok := flash.(*LoginProps) + if ok { + props.Username.Value = flashProps.Username.Value + + props.GeneralError = flashProps.GeneralError + props.Username.Error = flashProps.Username.Error + props.Password.Error = flashProps.Password.Error + } + + flash, err = sesh.GetFirstFlash(w, r, session, "successful_registration") + if err != nil { + http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError) + return + } + + successfulReg, _ := flash.(bool) + if successfulReg { + props.SuccessfulRegistration = true + } + + templates.Execute(w, "login.gotmpl", props) +} + +func (ctx *Context) 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") + + props := NewLoginProps() + props.Username.Value = username + + user, err := db.GetUserByName(username) + if user == nil || err != nil { + time.Sleep(time.Second) + props.GeneralError = "Username or password invalid" + ctx.RedirectWithFlash(w, r, "/", "login_props", &props) + return + } + + passHash, err := user.GetPassHash() + if err != nil { + props.GeneralError = "Something went wrong. Error code: Momo" + ctx.RedirectWithFlash(w, r, "/", "login_props", &props) + return + } + + err = bcrypt.CompareHashAndPassword(passHash, []byte(password)) + if err != nil { + props.GeneralError = "Username or password invalid" + ctx.RedirectWithFlash(w, r, "/", "login_props", &props) + return + } + + // NOTE: Overwriting any existing cookie or session here. So we don't care if there's an error + session, _ := ctx.store.Get(r, "lishwist_user") + session.ID = "" + 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/server/auth/logout.go b/server/routing/logout.go similarity index 72% rename from server/auth/logout.go rename to server/routing/logout.go index c4fcb0e..18b0ba8 100644 --- a/server/auth/logout.go +++ b/server/routing/logout.go @@ -1,11 +1,11 @@ -package auth +package routing import ( "net/http" ) -func (auth *AuthMiddleware) LogoutPost(w http.ResponseWriter, r *http.Request) { - session, err := auth.Store.Get(r, "lishwist_user") +func (ctx *Context) LogoutPost(w http.ResponseWriter, r *http.Request) { + session, err := ctx.store.Get(r, "lishwist_user") if err != nil { http.Error(w, "Something went wrong. Error code: Iroh", http.StatusInternalServerError) return diff --git a/server/auth/redirect_with_error.go b/server/routing/redirect_with_error.go similarity index 61% rename from server/auth/redirect_with_error.go rename to server/routing/redirect_with_error.go index 0bb00d5..a19e244 100644 --- a/server/auth/redirect_with_error.go +++ b/server/routing/redirect_with_error.go @@ -1,12 +1,12 @@ -package auth +package routing import ( "log" "net/http" ) -func (auth *AuthMiddleware) RedirectWithFlash(w http.ResponseWriter, r *http.Request, url string, key string, flash any) { - session, _ := auth.Store.Get(r, "lishwist_user") +func (ctx *Context) RedirectWithFlash(w http.ResponseWriter, r *http.Request, url string, key string, flash any) { + session, _ := ctx.store.Get(r, "lishwist_user") session.AddFlash(flash, key) if err := session.Save(r, w); err != nil { log.Println("Couldn't save session:", err) diff --git a/server/routing/register.go b/server/routing/register.go new file mode 100644 index 0000000..1fbb984 --- /dev/null +++ b/server/routing/register.go @@ -0,0 +1,70 @@ +package routing + +import ( + "encoding/json" + "lishwist/api" + "lishwist/templates" + "log" + "net/http" +) + +func (ctx *Context) Register(w http.ResponseWriter, r *http.Request) { + props := api.NewRegisterProps("", "", "") + + session, _ := ctx.store.Get(r, "lishwist_user") + if flashes := session.Flashes("register_props"); len(flashes) > 0 { + flashProps, _ := flashes[0].(*api.RegisterProps) + props.Username.Value = flashProps.Username.Value + + props.GeneralError = flashProps.GeneralError + props.Username.Error = flashProps.Username.Error + props.ConfirmPassword.Error = flashProps.ConfirmPassword.Error + } + + 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 + } + + templates.Execute(w, "register.gotmpl", props) +} + +func (ctx *Context) 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") + + props := api.Register(username, newPassword, confirmPassword) + + if props != nil { + ctx.RedirectWithFlash(w, r, "/register", "register_props", &props) + return + } + + ctx.RedirectWithFlash(w, r, "/", "successful_registration", true) +} + +type jsonParams struct { + Username string + NewPassword string + ConfirmPassword string +} + +func (ctx *Context) RegisterPostJson(w http.ResponseWriter, r *http.Request) { + var params jsonParams + err := decodeJsonParams(r, ¶ms) + if err != nil { + writeGeneralError(w, "Failed to decode json params: "+err.Error(), http.StatusBadRequest) + return + } + + props := api.Register(params.Username, params.NewPassword, params.ConfirmPassword) + + _ = json.NewEncoder(w).Encode(props) +} diff --git a/server/context/todo_update.go b/server/routing/todo.go similarity index 94% rename from server/context/todo_update.go rename to server/routing/todo.go index a6abd13..98ac86a 100644 --- a/server/context/todo_update.go +++ b/server/routing/todo.go @@ -1,4 +1,4 @@ -package context +package routing import ( "log" @@ -6,7 +6,7 @@ import ( ) func (ctx *Context) TodoUpdate(w http.ResponseWriter, r *http.Request) { - user := ctx.Auth.ExpectUser(r) + user := ctx.ExpectUser(r) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return diff --git a/server/context/context.go b/server/routing/wishlist.go similarity index 92% rename from server/context/context.go rename to server/routing/wishlist.go index 6d2336e..d78fba7 100644 --- a/server/context/context.go +++ b/server/routing/wishlist.go @@ -1,17 +1,12 @@ -package context +package routing import ( - "lishwist/auth" "lishwist/error" "net/http" ) -type Context struct { - Auth *auth.AuthMiddleware -} - func (ctx *Context) WishlistAdd(w http.ResponseWriter, r *http.Request) { - user := ctx.Auth.ExpectUser(r) + user := ctx.ExpectUser(r) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return @@ -26,7 +21,7 @@ func (ctx *Context) WishlistAdd(w http.ResponseWriter, r *http.Request) { } func (ctx *Context) WishlistDelete(w http.ResponseWriter, r *http.Request) { - user := ctx.Auth.ExpectUser(r) + user := ctx.ExpectUser(r) if err := r.ParseForm(); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return @@ -41,7 +36,7 @@ func (ctx *Context) WishlistDelete(w http.ResponseWriter, r *http.Request) { } func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request) { - user := ctx.Auth.ExpectUser(r) + user := ctx.ExpectUser(r) if err := r.ParseForm(); err != nil { error.Page(w, "Failed to parse form...", http.StatusBadRequest, err) return diff --git a/server/templates/base.gotmpl b/server/templates/base.gotmpl index 2ef3244..5407dc1 100644 --- a/server/templates/base.gotmpl +++ b/server/templates/base.gotmpl @@ -1,6 +1,6 @@ {{define "input"}} + value="{{.Value}}" {{if .Required}}required {{end}}{{if .MinLength}}minlength="{{.MinLength}}" {{end}} aria-describedby="{{if .Error}}{{.Name}}Error{{end}}"> {{with .Error}}