From 1ef6dcc4cf35e768e243ca4606740167f8510ee7 Mon Sep 17 00:00:00 2001 From: jordi fita mas Date: Wed, 26 Jul 2023 01:50:39 +0200 Subject: [PATCH] Get user from database based on cookie and serve login if not logged in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To get the user from the database i have to set the cookie first, that was already done in database.MustAcquire, but i thought they were too far apart, even thought they are so related. So, the cookie, and thus the role, is set when getting the user, that is actually the first thing to do once the connection is acquired. However, that way the database package has no knowledge of cookies, and the code that sets the cookie and retrieves the user are next to each other. I applied the same logic to the changes of locale.Match: it has not business knowing that the accept language string comes from a request; it only needs the actual string. Also, the TODO comment about getting the user’s locale made no sense, now, because app already knows that locale, so there is no need to pass the user to the locale package. Getting the locale is done after retrieving the user from the database, for the same reason the connection is Acquired as far up as possible: almost every request will need this value, together with the user and the database connection. I am a bit affraid that i will end up with functions that always expect these three values. Maybe i can put the locale inside user, as it is the user’s locale, after all, no matter if it came from the database or the user agent, but connection and user must be separate, i think. We’ll see. --- pkg/app/app.go | 76 +++++++++++++++++++++++++++++++--- pkg/app/login.go | 22 ++++++---- pkg/database/db.go | 11 ----- pkg/locale/locale.go | 29 +++++-------- po/ca.po | 23 ++++++---- po/es.po | 23 ++++++---- web/templates/dashboard.gohtml | 11 +++++ web/templates/login.gohtml | 5 +++ 8 files changed, 139 insertions(+), 61 deletions(-) create mode 100644 web/templates/dashboard.gohtml diff --git a/pkg/app/app.go b/pkg/app/app.go index f5915f7..b4097ae 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -6,14 +6,17 @@ package app import ( - "golang.org/x/text/language" + "context" "net/http" "path" "strings" + "golang.org/x/text/language" + "dev.tandem.ws/tandem/camper/pkg/database" httplib "dev.tandem.ws/tandem/camper/pkg/http" "dev.tandem.ws/tandem/camper/pkg/locale" + "dev.tandem.ws/tandem/camper/pkg/template" ) func shiftPath(p string) (head, tail string) { @@ -55,28 +58,43 @@ func New(db *database.DB) http.Handler { } func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { + requestURL := r.URL.Path var head string head, r.URL.Path = shiftPath(r.URL.Path) if head == "static" { h.fileHandler.ServeHTTP(w, r) } else { - cookie := getSessionCookie(r) - conn := h.db.MustAcquire(r.Context(), cookie) + conn, err := h.db.Acquire(r.Context()) + if err != nil { + panic(err) + } defer conn.Release() + cookie := getSessionCookie(r) + user, err := h.getUser(r.Context(), conn, cookie) + if err != nil { + panic(err) + } + l := h.matchLocale(r, user) + if head == "login" { switch r.Method { case http.MethodPost: - h.handleLogin(w, r, conn) + h.handleLogin(w, r, l, conn) default: methodNotAllowed(w, r, http.MethodPost) } } else { + if !user.LoggedIn { + h.serveLoginForm(w, r, l, requestURL) + return + } + switch head { case "": switch r.Method { case http.MethodGet: - h.handleGet(w, r) + h.serveDashboard(w, r, l) default: methodNotAllowed(w, r, http.MethodGet) } @@ -86,3 +104,51 @@ func (h *App) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } } + +type User struct { + Email string + LoggedIn bool + Role string + Language language.Tag + CsrfToken string +} + +func (h *App) getUser(ctx context.Context, conn *database.Conn, cookie string) (*User, error) { + if _, err := conn.Exec(ctx, "select set_cookie($1)", cookie); err != nil { + conn.Release() + return nil, err + } + + user := &User{ + Email: "", + LoggedIn: false, + Role: "guest", + } + row := conn.QueryRow(ctx, "select coalesce(email, ''), email is not null, role, lang_tag, csrf_token from user_profile") + var langTag string + if err := row.Scan(&user.Email, &user.LoggedIn, &user.Role, &langTag, &user.CsrfToken); err != nil { + return nil, err + } + if lang, err := language.Parse(langTag); err == nil { + user.Language = lang + } else { + return nil, err + } + + return user, nil +} + +func (h *App) matchLocale(r *http.Request, user *User) *locale.Locale { + l := h.locales[user.Language] + if l == nil { + l = locale.Match(r.Header.Get("Accept-Language"), h.locales, h.languageMatcher) + if l == nil { + l = h.defaultLocale + } + } + return l +} + +func (h *App) serveDashboard(w http.ResponseWriter, _ *http.Request, l *locale.Locale) { + template.MustRender(w, l, "dashboard.gohtml", nil) +} diff --git a/pkg/app/login.go b/pkg/app/login.go index 9646ceb..4d73e0f 100644 --- a/pkg/app/login.go +++ b/pkg/app/login.go @@ -24,6 +24,7 @@ const ( type loginForm struct { Email *form.Input Password *form.Input + Redirect *form.Input Error error } @@ -35,6 +36,9 @@ func newLoginForm() *loginForm { Password: &form.Input{ Name: "password", }, + Redirect: &form.Input{ + Name: "redirect", + }, } } @@ -44,6 +48,10 @@ func (f *loginForm) Parse(r *http.Request) error { } f.Email.FillValue(r) f.Password.FillValue(r) + f.Redirect.FillValue(r) + if f.Redirect.Val == "" { + f.Redirect.Val = "/" + } return nil } @@ -56,28 +64,24 @@ func (f *loginForm) Valid(l *locale.Locale) bool { return v.AllOK } -func (h *App) handleGet(w http.ResponseWriter, r *http.Request) { - l := h.matchLocale(r) +func (h *App) serveLoginForm(w http.ResponseWriter, _ *http.Request, l *locale.Locale, requestURL string) { login := newLoginForm() + login.Redirect.Val = requestURL + w.WriteHeader(http.StatusUnauthorized) template.MustRender(w, l, "login.gohtml", login) } -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, conn *database.Conn) { +func (h *App) handleLogin(w http.ResponseWriter, r *http.Request, l *locale.Locale, conn *database.Conn) { login := newLoginForm() if err := login.Parse(r); err != nil { http.Error(w, err.Error(), http.StatusBadRequest) return } - l := h.matchLocale(r) if login.Valid(l) { cookie := conn.MustGetText(r.Context(), "select login($1, $2, $3)", login.Email, login.Password, httplib.RemoteAddr(r)) if cookie != "" { setSessionCookie(w, cookie) - http.Redirect(w, r, "/", http.StatusSeeOther) + http.Redirect(w, r, login.Redirect.Val, http.StatusSeeOther) return } login.Error = errors.New(l.Gettext("Invalid user or password.")) diff --git a/pkg/database/db.go b/pkg/database/db.go index 14e8d8a..818afce 100644 --- a/pkg/database/db.go +++ b/pkg/database/db.go @@ -53,17 +53,6 @@ func (db *DB) Acquire(ctx context.Context) (*Conn, error) { return &Conn{conn}, nil } -func (db *DB) MustAcquire(ctx context.Context, cookie string) *Conn { - conn, err := db.Acquire(ctx) - if err != nil { - panic(err) - } - if _, err = conn.Exec(ctx, "select set_cookie($1)", cookie); err != nil { - panic(false) - } - return conn -} - type Conn struct { *pgxpool.Conn } diff --git a/pkg/locale/locale.go b/pkg/locale/locale.go index 5d96797..1074ad6 100644 --- a/pkg/locale/locale.go +++ b/pkg/locale/locale.go @@ -7,8 +7,6 @@ package locale import ( "context" - "net/http" - "github.com/leonelquinteros/gotext" "golang.org/x/text/language" @@ -60,23 +58,18 @@ func (l *Locale) GettextNoop(str string) string { return str } -func Match(r *http.Request, locales Locales, defaultLocale *Locale, matcher language.Matcher) *Locale { - var locale *Locale - // TODO: find user locale - if locale == nil { - t, _, err := language.ParseAcceptLanguage(r.Header.Get("Accept-Language")) - if err == nil { - tag, _, _ := matcher.Match(t...) - var ok bool - locale, ok = locales[tag] - for !ok && !tag.IsRoot() { - tag = tag.Parent() - locale, ok = locales[tag] - } - } +func Match(acceptLanguage string, locales Locales, matcher language.Matcher) *Locale { + t, _, err := language.ParseAcceptLanguage(acceptLanguage) + if err != nil { + return nil } - if locale == nil { - locale = defaultLocale + var locale *Locale + tag, _, _ := matcher.Match(t...) + var ok bool + locale, ok = locales[tag] + for !ok && !tag.IsRoot() { + tag = tag.Parent() + locale, ok = locales[tag] } return locale } diff --git a/po/ca.po b/po/ca.po index f31fc43..68e3ef4 100644 --- a/po/ca.po +++ b/po/ca.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2023-07-24 17:04+0200\n" +"POT-Creation-Date: 2023-07-26 01:33+0200\n" "PO-Revision-Date: 2023-07-22 23:45+0200\n" "Last-Translator: jordi fita mas \n" "Language-Team: Catalan \n" @@ -18,22 +18,27 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: web/templates/login.gohtml:2 web/templates/login.gohtml:8 +#: web/templates/dashboard.gohtml:2 web/templates/dashboard.gohtml:6 +msgctxt "title" +msgid "Dashboard" +msgstr "Tauler" + +#: web/templates/login.gohtml:2 web/templates/login.gohtml:9 msgctxt "title" msgid "Login" msgstr "Entrada" -#: web/templates/login.gohtml:17 +#: web/templates/login.gohtml:18 msgctxt "input" msgid "Email" msgstr "Correu-e" -#: web/templates/login.gohtml:26 +#: web/templates/login.gohtml:27 msgctxt "input" msgid "Password" msgstr "Contrasenya" -#: web/templates/login.gohtml:35 +#: web/templates/login.gohtml:36 msgctxt "action" msgid "Login" msgstr "Entra" @@ -42,18 +47,18 @@ msgstr "Entra" msgid "Skip to main content" msgstr "Salta al contingut principal" -#: pkg/app/login.go:50 +#: pkg/app/login.go:60 msgid "Email can not be empty." msgstr "No podeu deixar el correu en blanc." -#: pkg/app/login.go:51 +#: pkg/app/login.go:61 msgid "This email is not valid. It should be like name@domain.com." msgstr "Aquest correu-e no és vàlid. Hauria de ser similar a nom@domini.com." -#: pkg/app/login.go:53 +#: pkg/app/login.go:63 msgid "Password can not be empty." msgstr "No podeu deixar la contrasenya en blanc." -#: pkg/app/login.go:82 +#: pkg/app/login.go:86 msgid "Invalid user or password." msgstr "Nom d’usuari o contrasenya incorrectes." diff --git a/po/es.po b/po/es.po index 5ccfb31..3eb1bea 100644 --- a/po/es.po +++ b/po/es.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: camper\n" "Report-Msgid-Bugs-To: jordi@tandem.blog\n" -"POT-Creation-Date: 2023-07-24 17:04+0200\n" +"POT-Creation-Date: 2023-07-26 01:33+0200\n" "PO-Revision-Date: 2023-07-22 23:46+0200\n" "Last-Translator: jordi fita mas \n" "Language-Team: Spanish \n" @@ -18,22 +18,27 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -#: web/templates/login.gohtml:2 web/templates/login.gohtml:8 +#: web/templates/dashboard.gohtml:2 web/templates/dashboard.gohtml:6 +msgctxt "title" +msgid "Dashboard" +msgstr "Panel" + +#: web/templates/login.gohtml:2 web/templates/login.gohtml:9 msgctxt "title" msgid "Login" msgstr "Entrada" -#: web/templates/login.gohtml:17 +#: web/templates/login.gohtml:18 msgctxt "input" msgid "Email" msgstr "Correo-e" -#: web/templates/login.gohtml:26 +#: web/templates/login.gohtml:27 msgctxt "input" msgid "Password" msgstr "Contraseña" -#: web/templates/login.gohtml:35 +#: web/templates/login.gohtml:36 msgctxt "action" msgid "Login" msgstr "Entrar" @@ -42,18 +47,18 @@ msgstr "Entrar" msgid "Skip to main content" msgstr "Saltar al contenido principal" -#: pkg/app/login.go:50 +#: pkg/app/login.go:60 msgid "Email can not be empty." msgstr "No podéis dejar el correo-e en blanco." -#: pkg/app/login.go:51 +#: pkg/app/login.go:61 msgid "This email is not valid. It should be like name@domain.com." msgstr "Este correo-e no es válido. Tiene que ser parecido a nombre@dominio.com." -#: pkg/app/login.go:53 +#: pkg/app/login.go:63 msgid "Password can not be empty." msgstr "No podéis dejar la contraseña en blanco." -#: pkg/app/login.go:82 +#: pkg/app/login.go:86 msgid "Invalid user or password." msgstr "Usuario o contraseña incorrectos." diff --git a/web/templates/dashboard.gohtml b/web/templates/dashboard.gohtml new file mode 100644 index 0000000..39186bf --- /dev/null +++ b/web/templates/dashboard.gohtml @@ -0,0 +1,11 @@ + +{{ define "title" -}} + {{( pgettext "Dashboard" "title" )}} +{{- end }} + +{{ define "content" -}} +

{{( pgettext "Dashboard" "title" )}}

+{{- end }} diff --git a/web/templates/login.gohtml b/web/templates/login.gohtml index f4642e9..9b94c62 100644 --- a/web/templates/login.gohtml +++ b/web/templates/login.gohtml @@ -1,3 +1,7 @@ + {{ define "title" -}} {{( pgettext "Login" "title" )}} {{- end }} @@ -5,6 +9,7 @@ {{ define "content" -}} {{- /*gotype: dev.tandem.ws/tandem/camper/pkg/app.loginForm */ -}}
+

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

{{ if .Error -}}