package pkg import ( "context" "net" "net/http" "time" ) const ( ContextUserKey = "numerus-user" ContextCookieKey = "numerus-cookie" ContextConnKey = "numerus-database" sessionCookie = "numerus-session" defaultRole = "guest" ) type LoginPage struct { LoginError bool Email string Password string } type AppUser struct { Email string LoggedIn bool Role string } func LoginHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := getUser(r) if user.LoggedIn { http.Redirect(w, r, "/", http.StatusSeeOther) return } r.ParseForm() page := LoginPage{ Email: r.FormValue("email"), Password: r.FormValue("password"), } if r.Method == "POST" { conn := getConn(r) cookie := conn.MustGetText(r.Context(), "", "select login($1, $2, $3)", page.Email, page.Password, remoteAddr(r)) if cookie != "" { http.SetCookie(w, createSessionCookie(cookie, 8766*24*time.Hour)) http.Redirect(w, r, "/", http.StatusSeeOther) return } w.WriteHeader(http.StatusUnauthorized) page.LoginError = true } else { w.WriteHeader(http.StatusOK) } mustRenderWebTemplate(w, r, "login.html", page) }) } func LogoutHandler() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { user := getUser(r) if user.LoggedIn { conn := getConn(r) conn.MustExec(r.Context(), "select logout()") http.SetCookie(w, createSessionCookie("", -24*time.Hour)) } http.Redirect(w, r, "/login", 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) { var ctx = r.Context() if cookie, err := r.Cookie(sessionCookie); err == nil { ctx = context.WithValue(ctx, ContextCookieKey, cookie.Value) } conn := db.MustAcquire(ctx) defer conn.Release() ctx = context.WithValue(ctx, ContextConnKey, conn) user := &AppUser{ Email: "", LoggedIn: false, Role: defaultRole, } row := conn.QueryRow(ctx, "select current_setting('request.user.email', true), current_user") if err := row.Scan(&user.Email, &user.Role); err != nil { panic(err) } user.LoggedIn = user.Email != "" ctx = context.WithValue(ctx, ContextUserKey, user) next.ServeHTTP(w, r.WithContext(ctx)) }) } func getUser(r *http.Request) *AppUser { return r.Context().Value(ContextUserKey).(*AppUser) } func getConn(r *http.Request) *Conn { return r.Context().Value(ContextConnKey).(*Conn) }