diff --git a/pkg/app/app.go b/pkg/app/app.go index dda379f..56afa90 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -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, + } +} diff --git a/pkg/database/db.go b/pkg/database/db.go index 1d82fe9..3f8fe25 100644 --- a/pkg/database/db.go +++ b/pkg/database/db.go @@ -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 +} diff --git a/web/templates/login.gohtml b/web/templates/login.gohtml index cf1208d..09b861e 100644 --- a/web/templates/login.gohtml +++ b/web/templates/login.gohtml @@ -5,13 +5,24 @@ {{ define "content" -}}

{{( pgettext "Login" "title" )}}

+ {{ if . -}} + + {{- end }}
- - -
- - +
- +
{{- end }}