Handle the login form
I now actually handle the /login URL and check whether the email and password are valid, creating the session cookie if correct, but doing nothing else with that cookie, for now. The validation is done by hand for now, because i do not yet how i will actually do it without so much duplication.
This commit is contained in:
parent
25bf186a83
commit
2300735030
|
@ -6,18 +6,25 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/mail"
|
||||
"path"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
||||
middleware "dev.tandem.ws/tandem/camper/pkg/http"
|
||||
httplib "dev.tandem.ws/tandem/camper/pkg/http"
|
||||
"dev.tandem.ws/tandem/camper/pkg/locale"
|
||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||
)
|
||||
|
||||
const (
|
||||
sessionCookie = "camper-session"
|
||||
)
|
||||
|
||||
func shiftPath(p string) (head, tail string) {
|
||||
p = path.Clean("/" + p)
|
||||
if i := strings.IndexByte(p[1:], '/') + 1; i <= 0 {
|
||||
|
@ -51,8 +58,8 @@ func New(db *database.DB) http.Handler {
|
|||
}
|
||||
|
||||
var handler http.Handler = app
|
||||
handler = middleware.RecoverPanic(handler)
|
||||
handler = middleware.LogRequest(handler)
|
||||
handler = httplib.RecoverPanic(handler)
|
||||
handler = httplib.LogRequest(handler)
|
||||
return handler
|
||||
}
|
||||
|
||||
|
@ -62,6 +69,13 @@ func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
switch head {
|
||||
case "static":
|
||||
h.fileHandler.ServeHTTP(w, r)
|
||||
case "login":
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.handleLogin(w, r)
|
||||
default:
|
||||
methodNotAllowed(w, r, http.MethodPost)
|
||||
}
|
||||
case "":
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
|
@ -75,6 +89,58 @@ func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (h *App) handleGet(w http.ResponseWriter, r *http.Request) {
|
||||
l := locale.Match(r, h.locales, h.defaultLocale, h.languageMatcher)
|
||||
l := h.matchLocale(r)
|
||||
template.MustRender(w, l, "login.gohtml", nil)
|
||||
}
|
||||
|
||||
func (h *App) matchLocale(r *http.Request) *locale.Locale {
|
||||
return locale.Match(r, h.locales, h.defaultLocale, h.languageMatcher)
|
||||
}
|
||||
|
||||
func (h *App) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if err := r.ParseForm(); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var errs []error
|
||||
l := h.matchLocale(r)
|
||||
email := strings.TrimSpace(r.FormValue("email"))
|
||||
if email == "" {
|
||||
errs = append(errs, errors.New(l.Get("Email can not be empty.")))
|
||||
} else if _, err := mail.ParseAddress(email); err != nil {
|
||||
errs = append(errs, errors.New(l.Get("This email is not valid. It should be like name@domain.com.")))
|
||||
}
|
||||
password := strings.TrimSpace(r.FormValue("password"))
|
||||
if password == "" {
|
||||
errs = append(errs, errors.New(l.Get("Password can not be empty.")))
|
||||
}
|
||||
if errs == nil {
|
||||
conn := h.db.MustAcquire(r.Context())
|
||||
cookie := conn.MustGetText(r.Context(), "select login($1, $2, $3)", email, password, httplib.RemoteAddr(r))
|
||||
if cookie != "" {
|
||||
setSessionCookie(w, cookie)
|
||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
errs = append(errs, errors.New(l.Get("Invalid user or password.")))
|
||||
w.WriteHeader(http.StatusUnauthorized)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusUnprocessableEntity)
|
||||
}
|
||||
template.MustRender(w, l, "login.gohtml", errs)
|
||||
}
|
||||
|
||||
func setSessionCookie(w http.ResponseWriter, cookie string) {
|
||||
http.SetCookie(w, createSessionCookie(cookie, 8766*24*time.Hour))
|
||||
}
|
||||
|
||||
func createSessionCookie(value string, duration time.Duration) *http.Cookie {
|
||||
return &http.Cookie{
|
||||
Name: sessionCookie,
|
||||
Value: value,
|
||||
Path: "/",
|
||||
Expires: time.Now().Add(duration),
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,3 +44,31 @@ func New(ctx context.Context, connString string) (*DB, error) {
|
|||
type DB struct {
|
||||
*pgxpool.Pool
|
||||
}
|
||||
|
||||
func (db *DB) Acquire(ctx context.Context) (*Conn, error) {
|
||||
conn, err := db.Pool.Acquire(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Conn{conn}, nil
|
||||
}
|
||||
|
||||
func (db *DB) MustAcquire(ctx context.Context) *Conn {
|
||||
conn, err := db.Acquire(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return conn
|
||||
}
|
||||
|
||||
type Conn struct {
|
||||
*pgxpool.Conn
|
||||
}
|
||||
|
||||
func (c *Conn) MustGetText(ctx context.Context, sql string, args ...interface{}) string {
|
||||
var result string
|
||||
if err := c.QueryRow(ctx, sql, args...).Scan(&result); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
|
|
@ -5,13 +5,24 @@
|
|||
{{ define "content" -}}
|
||||
<form method="post" action="/login">
|
||||
<h2>{{( pgettext "Login" "title" )}}</h2>
|
||||
{{ if . -}}
|
||||
<div class="message" role="alert">
|
||||
{{ range . -}}
|
||||
<p>{{ . }}</p>
|
||||
{{- end }}
|
||||
</div>
|
||||
{{- end }}
|
||||
<fieldset>
|
||||
<label for="email">{{( pgettext "Email" "input" )}}</label>
|
||||
<input id="email" type="email" name="username" autocomplete="username" required autofocus>
|
||||
<br>
|
||||
<label for="current-password">{{( pgettext "Password" "input" )}}</label>
|
||||
<input id="current-password" type="password" name="password" autocomplete="current-password" required>
|
||||
<label>
|
||||
{{( pgettext "Email" "input" )}}<br>
|
||||
<input type="email" name="email" autocomplete="username" required autofocus><br>
|
||||
</label><label>
|
||||
{{( pgettext "Password" "input" )}}<br>
|
||||
<input type="password" name="password" autocomplete="current-password" required><br>
|
||||
</label>
|
||||
</fieldset>
|
||||
<footer>
|
||||
<button type="submit">{{( pgettext "Login" "action" )}}</button>
|
||||
</footer>
|
||||
</form>
|
||||
{{- end }}
|
||||
|
|
Loading…
Reference in New Issue