package pkg import ( "context" "html/template" "net" "net/http" "time" "github.com/jackc/pgx/v4" ) const ( ContextUserKey = "numerus-user" sessionCookie = "numerus-session" defaultRole = "guest" ) type LoginPage struct { LoginError string Email string Password string } type AppUser struct { Email string LoggedIn bool Role string } func LoginHandler(db *Db) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := getUser(r) if user.LoggedIn { http.Redirect(w, r, "/", http.StatusSeeOther) } else { r.ParseForm() page := LoginPage{ Email: r.FormValue("email"), Password: r.FormValue("password"), } cookie := db.Text(r, "", "select login($1, $2, $3)", page.Email, page.Password, remoteAddr(r)) if cookie == "" { page.LoginError = "Invalid user or password" w.WriteHeader(http.StatusUnauthorized) t, err := template.ParseFiles("web/template/login.html") if err != nil { panic(err) } err = t.Execute(w, page) if err != nil { panic(err) } } else { http.SetCookie(w, createSessionCookie(cookie, 8766*24*time.Hour)) http.Redirect(w, r, "/", http.StatusSeeOther) } } }) } func LogoutHandler(db *Db) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := getUser(r) if user.LoggedIn { db.Exec(r, "select logout()") http.SetCookie(w, createSessionCookie("", -24*time.Hour)) } http.Redirect(w, r, "/", http.StatusSeeOther) }) } func remoteAddr(r *http.Request) string { address := r.Header.Get("X-Forwarded-For") if address == "" { address, _, _ = net.SplitHostPort(r.RemoteAddr) } return address } 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, } } func CheckLogin(db *Db, next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := &AppUser{ Email: "", LoggedIn: false, Role: defaultRole, } if cookie, err := r.Cookie(sessionCookie); err == nil { row := db.pool.QueryRow(r.Context(), "select * from check_cookie($1) as (email text, role name)", cookie.Value) if err := row.Scan(&user.Email, &user.Role); err != nil { if err != pgx.ErrNoRows { panic(err) } } else { user.LoggedIn = user.Role != "guest" } } ctx := context.WithValue(r.Context(), ContextUserKey, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } func getUser(r *http.Request) *AppUser { return r.Context().Value(ContextUserKey).(*AppUser) }