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
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/mail"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
|
||||||
"dev.tandem.ws/tandem/camper/pkg/database"
|
"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/locale"
|
||||||
"dev.tandem.ws/tandem/camper/pkg/template"
|
"dev.tandem.ws/tandem/camper/pkg/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sessionCookie = "camper-session"
|
||||||
|
)
|
||||||
|
|
||||||
func shiftPath(p string) (head, tail string) {
|
func shiftPath(p string) (head, tail string) {
|
||||||
p = path.Clean("/" + p)
|
p = path.Clean("/" + p)
|
||||||
if i := strings.IndexByte(p[1:], '/') + 1; i <= 0 {
|
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
|
var handler http.Handler = app
|
||||||
handler = middleware.RecoverPanic(handler)
|
handler = httplib.RecoverPanic(handler)
|
||||||
handler = middleware.LogRequest(handler)
|
handler = httplib.LogRequest(handler)
|
||||||
return handler
|
return handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +69,13 @@ func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
switch head {
|
switch head {
|
||||||
case "static":
|
case "static":
|
||||||
h.fileHandler.ServeHTTP(w, r)
|
h.fileHandler.ServeHTTP(w, r)
|
||||||
|
case "login":
|
||||||
|
switch r.Method {
|
||||||
|
case http.MethodPost:
|
||||||
|
h.handleLogin(w, r)
|
||||||
|
default:
|
||||||
|
methodNotAllowed(w, r, http.MethodPost)
|
||||||
|
}
|
||||||
case "":
|
case "":
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodGet:
|
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) {
|
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)
|
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 {
|
type DB struct {
|
||||||
*pgxpool.Pool
|
*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" -}}
|
{{ define "content" -}}
|
||||||
<form method="post" action="/login">
|
<form method="post" action="/login">
|
||||||
<h2>{{( pgettext "Login" "title" )}}</h2>
|
<h2>{{( pgettext "Login" "title" )}}</h2>
|
||||||
|
{{ if . -}}
|
||||||
|
<div class="message" role="alert">
|
||||||
|
{{ range . -}}
|
||||||
|
<p>{{ . }}</p>
|
||||||
|
{{- end }}
|
||||||
|
</div>
|
||||||
|
{{- end }}
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<label for="email">{{( pgettext "Email" "input" )}}</label>
|
<label>
|
||||||
<input id="email" type="email" name="username" autocomplete="username" required autofocus>
|
{{( pgettext "Email" "input" )}}<br>
|
||||||
<br>
|
<input type="email" name="email" autocomplete="username" required autofocus><br>
|
||||||
<label for="current-password">{{( pgettext "Password" "input" )}}</label>
|
</label><label>
|
||||||
<input id="current-password" type="password" name="password" autocomplete="current-password" required>
|
{{( pgettext "Password" "input" )}}<br>
|
||||||
|
<input type="password" name="password" autocomplete="current-password" required><br>
|
||||||
|
</label>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
<footer>
|
||||||
<button type="submit">{{( pgettext "Login" "action" )}}</button>
|
<button type="submit">{{( pgettext "Login" "action" )}}</button>
|
||||||
|
</footer>
|
||||||
</form>
|
</form>
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
Loading…
Reference in New Issue