From dd4c74598d2c8b7f88e31fffc1353fc46dd7d748 Mon Sep 17 00:00:00 2001 From: lbenedar Date: Fri, 6 Mar 2026 14:01:57 +0300 Subject: [PATCH] ch11.4 --- cmd/web/handlers.go | 52 +++++++++++++++++++++++++++++++-- internal/models/users.go | 22 +++++++++++++- internal/validator/validator.go | 9 ++++-- ui/html/pages/login.tmpl | 26 +++++++++++++++++ 4 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 ui/html/pages/login.tmpl diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go index d7cc3e7..f32cc1c 100644 --- a/cmd/web/handlers.go +++ b/cmd/web/handlers.go @@ -26,6 +26,12 @@ type userSignupForm struct { validator.Validator `form:"-"` } +type userLoginForm struct { + Email string `form:"email"` + Password string `form:"password"` + validator.Validator `form:"-"` +} + func (app *application) render(w http.ResponseWriter, status int, page string, data *templateData) { ts, ok := app.templateCache[page] if !ok { @@ -164,7 +170,47 @@ func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) { } func (app *application) userLoginPost(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Login the user...") + var form userLoginForm + + err := app.decodePostForm(r, &form) + if err != nil { + app.clientError(w, http.StatusBadRequest) + return + } + + form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank") + form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address") + form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank") + + if !form.Valid() { + data := app.newTemplateData(r) + data.Form = form + app.render(w, http.StatusUnprocessableEntity, "login.tmpl", data) + return + } + + id, err := app.users.Authenticate(form.Email, form.Password) + if err != nil { + if errors.Is(err, models.ErrInvalidCredentials) { + form.AddNonFieldError("Email or password is incorrect") + + data := app.newTemplateData(r) + data.Form = form + app.render(w, http.StatusUnprocessableEntity, "login.tmpl", data) + } else { + app.serverError(w, err) + } + return + } + err = app.sessionManager.RenewToken(r.Context()) + if err != nil { + app.serverError(w, err) + return + } + + app.sessionManager.Put(r.Context(), "authenticatedUserID", id) + + http.Redirect(w, r, "/snippet/create", http.StatusSeeOther) } func (app *application) userLogoutPost(w http.ResponseWriter, r *http.Request) { @@ -178,5 +224,7 @@ func (app *application) userSignup(w http.ResponseWriter, r *http.Request) { } func (app *application) userLogin(w http.ResponseWriter, r *http.Request) { - fmt.Fprintln(w, "Display a HTML for log in a user...") + data := app.newTemplateData(r) + data.Form = userLoginForm{} + app.render(w, http.StatusOK, "login.tmpl", data) } diff --git a/internal/models/users.go b/internal/models/users.go index 295cf7d..7b6196c 100644 --- a/internal/models/users.go +++ b/internal/models/users.go @@ -45,7 +45,27 @@ func (m *UserModel) Insert(name, email, password string) error { } func (m *UserModel) Authenticate(email, password string) (int, error) { - return 0, nil + var id int + var hashedPassword []byte + + stmt := `SELECT id, hashed_password FROM users WHERE email = ?` + + err := m.DB.QueryRow(stmt, email).Scan(&id, &hashedPassword) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return 0, ErrInvalidCredentials + } + return 0, err + } + err = bcrypt.CompareHashAndPassword(hashedPassword, []byte(password)) + if err != nil { + if errors.Is(err, bcrypt.ErrMismatchedHashAndPassword) { + return 0, ErrInvalidCredentials + } + return 0, err + } + + return id, nil } func (m *UserModel) Exists(id int) (bool, error) { diff --git a/internal/validator/validator.go b/internal/validator/validator.go index 5d3956a..2bcb7e1 100644 --- a/internal/validator/validator.go +++ b/internal/validator/validator.go @@ -7,11 +7,12 @@ import ( ) type Validator struct { - FieldErrors map[string]string + NonFieldErrors []string + FieldErrors map[string]string } func (v *Validator) Valid() bool { - return len(v.FieldErrors) == 0 + return len(v.FieldErrors) == 0 && len(v.NonFieldErrors) == 0 } func (v *Validator) AddFieldError(key, message string) { @@ -23,6 +24,10 @@ func (v *Validator) AddFieldError(key, message string) { } } +func (v *Validator) AddNonFieldError(message string) { + v.NonFieldErrors = append(v.NonFieldErrors, message) +} + func (v *Validator) CheckField(ok bool, key, message string) { if !ok { v.AddFieldError(key, message) diff --git a/ui/html/pages/login.tmpl b/ui/html/pages/login.tmpl new file mode 100644 index 0000000..c7d6e22 --- /dev/null +++ b/ui/html/pages/login.tmpl @@ -0,0 +1,26 @@ +{{define "title"}}Login{{end}} + +{{define "main"}} +
+ {{range .Form.NonFieldErrors}} +
{{.}}
+ {{end}} +
+ + {{with .Form.FieldErrors.email}} + + {{end}} + +
+
+ + {{with .Form.FieldErrors.password}} + + {{end}} + +
+
+ +
+
+{{end}} \ No newline at end of file