diff options
author | René 'Necoro' Neumann <necoro@necoro.eu> | 2024-10-17 00:27:08 +0200 |
---|---|---|
committer | René 'Necoro' Neumann <necoro@necoro.eu> | 2024-10-17 00:27:08 +0200 |
commit | 869fb9691f877116d5b15a92de006d0daf4d70e5 (patch) | |
tree | 2493c72172d5817ec9deec36229a84b687eb3190 /pages/login.go | |
parent | 6fc180ba6d9bc5c32340466988d9e26f8d6e3c5c (diff) | |
download | gosten-869fb9691f877116d5b15a92de006d0daf4d70e5.tar.gz gosten-869fb9691f877116d5b15a92de006d0daf4d70e5.tar.bz2 gosten-869fb9691f877116d5b15a92de006d0daf4d70e5.zip |
Restructure and change to chi as muxing framework
Diffstat (limited to 'pages/login.go')
-rw-r--r-- | pages/login.go | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/pages/login.go b/pages/login.go new file mode 100644 index 0000000..fb7859a --- /dev/null +++ b/pages/login.go @@ -0,0 +1,123 @@ +package pages + +import ( + "context" + "database/sql" + "errors" + "gosten/csrf" + "gosten/form" + "gosten/session" + "log" + "net/http" + "net/url" + + "golang.org/x/crypto/bcrypt" +) + +type userContextKey struct{} + +const ( + sessionDuration = 86400 * 7 // 7 days + loginQueryMarker = "next" +) + +func RequireAuth(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + s := session.From(r) + + if !s.IsNew() && s.Authenticated { + u, err := Q.GetUserById(r.Context(), s.UserID) + if err == nil { + // authenticated --> done + ctx := context.WithValue(r.Context(), userContextKey{}, u.ID) + next.ServeHTTP(w, r.WithContext(ctx)) + return + } + + s.Invalidate() + s.Save(w, r) + } + + // redirect to login with next-param + v := url.Values{} + v.Set(loginQueryMarker, r.URL.Path) + redirPath := "/login?" + v.Encode() + http.Redirect(w, r, redirPath, http.StatusFound) + }) +} + +type User struct { + Name string `form:"options=required,autofocus"` + Password string `form:"type=password;options=required"` + RememberMe bool `form:"type=checkbox;value=y;options=checked"` + Errors []error `form:"-"` + csrf.Csrf +} + +func showLoginPage(w http.ResponseWriter, u User) { + showTemplate(w, "login", u) +} + +func Login() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if session.From(r).Authenticated { + http.Redirect(w, r, "/", http.StatusFound) + } + u := User{} + u.SetCsrfField(r) + showLoginPage(w, u) + } +} + +func userId(r *http.Request) int32 { + return r.Context().Value(userContextKey{}).(int32) +} + +func checkLogin(ctx context.Context, user User) (bool, int32) { + dbUser, err := Q.GetUserByName(ctx, user.Name) + if err == nil { + hash := []byte(dbUser.Pwd) + pwd := []byte(user.Password) + + if bcrypt.CompareHashAndPassword(hash, pwd) != nil { + return false, 0 + } + } else if errors.Is(err, sql.ErrNoRows) { + return false, 0 + } else { + log.Panicf("Could not load user '%s': %v", user.Name, err) + } + + return true, dbUser.ID +} + +func HandleLogin(w http.ResponseWriter, r *http.Request) { + u := User{} + form.Parse(r, &u) + + ok, userId := checkLogin(r.Context(), u) + + if !ok { + u.Errors = []error{form.FieldError{Field: "Password", Issue: "Invalid"}} + showLoginPage(w, u) + return + } + + s := session.From(r) + if u.RememberMe { + s.MaxAge(sessionDuration) // 1 week + } else { + s.MaxAge(0) + } + + s.UserID = userId + s.Authenticated = true + s.Save(w, r) + + // redirect + next := r.URL.Query().Get(loginQueryMarker) + if next == "" { + next = "/" + } + http.Redirect(w, r, next, http.StatusFound) +} |