pcmt/handlers/handlers.go
leo 58c9446130
All checks were successful
continuous-integration/drone/push Build is passing
handlers: clean up deadcode
2023-04-19 03:48:47 +02:00

501 lines
11 KiB
Go

package handlers
import (
"context"
"errors"
"fmt"
"html/template"
"io/fs"
"net/http"
"path/filepath"
"strings"
"git.dotya.ml/mirre-mt/pcmt/ent"
moduser "git.dotya.ml/mirre-mt/pcmt/modules/user"
"github.com/labstack/echo/v4"
)
type tplMap map[string]*template.Template
var (
tmpls = template.New("")
templateMap tplMap
tmplFS fs.FS
)
func listAllTmpls() []string {
files, err := filepath.Glob("templates/*.tmpl")
if err != nil {
panic(err)
}
for i, v := range files {
files[i] = strings.TrimPrefix(v, "templates/")
}
log.Info("returning files")
return files
}
func setFuncMap(t *template.Template) {
t = t.Funcs(template.FuncMap{
"ifIE": func() template.HTML { return template.HTML("<!--[if IE]>") },
"endifIE": func() template.HTML { return template.HTML("<![endif]>") },
"htmlSafe": func(html string) template.HTML {
return template.HTML(html)
},
"pageIs": func(want, got string) bool {
if want == got {
return true
}
return false
},
})
}
func getFuncMap() []template.FuncMap {
return []template.FuncMap{
map[string]interface{}{
"ifIE": func() template.HTML { return template.HTML("<!--[if IE]>") },
"endifIE": func() template.HTML { return template.HTML("<![endif]>") },
"htmlSafe": func(html string) template.HTML {
return template.HTML(html)
},
"pageIs": func(want, got string) bool {
if want == got {
return true
}
return false
},
},
}
}
func initTemplates(f fs.FS) {
if f != nil || f != tmplFS {
tmplFS = f
}
// for _, funcs := range getFuncMap() {
// // log.Println(funcs)
// tmpls.Funcs(funcs)
// }
setFuncMap(tmpls)
allTmpls := listAllTmpls()
// ensure this fails at compile time, if at all ("Must").
tmpls = template.Must(tmpls.ParseFS(tmplFS, allTmpls...))
makeTplMap(tmpls)
}
func makeTplMap(tpl *template.Template) {
allTmpls := listAllTmpls()
var tp tplMap
if templateMap == nil {
tp = make(tplMap, len(allTmpls))
} else {
tp = templateMap
}
for _, v := range allTmpls {
tp[v] = tpl.Lookup(v)
}
templateMap = tp
}
func getTmpl(name string) *template.Template {
liveMode := conf.LiveMode
if liveMode {
log.Info("re-reading tmpls")
tpl := template.New("")
setFuncMap(tpl)
allTmpls := listAllTmpls()
// ensure this fails at compile time, if at all ("Must").
tmpls = template.Must(tpl.ParseFS(tmplFS, allTmpls...))
}
return tmpls.Lookup(name)
}
func Admin() echo.HandlerFunc {
return func(c echo.Context) error {
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials")
}
}
func Index() echo.HandlerFunc {
return func(c echo.Context) error {
// tpl, err := template.New("index").ParseFS(templates, "index.tmpl")
// tpl := template.New("index")
// tpl := theTmpls.New("index.tmpl")
// tpl := tmpls.Lookup("index.tmpl")
tpl := getTmpl("index.tmpl")
// if conf.LiveMode {
// // reload tmpls.
// LoadTemplates(tmplFS)
// }
// tpl := templateMap["index.tmpl"]
csrf := c.Get("csrf").(string)
err := tpl.Execute(c.Response().Writer,
page{
AppName: conf.AppName,
AppVer: appver,
Title: "Welcome!",
CSRF: csrf,
DevelMode: conf.DevelMode,
Current: "home",
},
)
if err != nil {
log.Errorf("error when executing tmpl: '%q'", err)
return err
}
return nil
}
}
func Signin() echo.HandlerFunc {
return func(c echo.Context) error {
session, err := c.Cookie("session")
if err != nil {
if err == http.ErrNoCookie {
log.Info("no session cookie found")
}
}
if session != nil {
log.Info("got session")
// if err := session.Valid(); err == nil && session.Expires.After(time.Now()) {
if err := session.Valid(); err == nil {
c.Redirect(302, "/home")
} else {
log.Warn("invalid (or expired) session", "error", err.Error())
}
}
tpl := getTmpl("signin.tmpl")
err = tpl.Execute(c.Response().Writer,
page{
AppName: conf.AppName,
AppVer: appver,
Title: "Sign in",
DevelMode: conf.DevelMode,
Current: "signin",
},
)
if err != nil {
return err
}
return nil
}
}
func SigninPost(client *ent.Client) echo.HandlerFunc {
return func(c echo.Context) error {
ct := c.Request().Header.Get("Content-Type")
fmt.Printf("signin(POST): content-type: %s\n", ct)
err := c.Request().ParseForm()
if err != nil {
return err
}
var username string
var password string
if uname := c.Request().FormValue("username"); uname != "" {
username = uname
log.Infof("authenticating user '%s' at /signin", username)
} else {
log.Info("username was not set, returning to /signin")
c.Redirect(302, "/signin")
return nil
}
if passwd := c.Request().FormValue("password"); passwd != "" {
password = passwd
} else {
log.Info("password was not set, returning to /signin")
c.Redirect(302, "/signin")
return nil
}
ctx := context.WithValue(context.Background(), "logger", log)
if usr, err := moduser.QueryUser(ctx, client, username); err == nil {
log.Info("queried user:", &usr.ID)
if usr.Password != password {
log.Warn("wrong user credentials, redirecting to /signin")
c.Redirect(302, "/signin")
}
} else {
// just log the error instead of returning it to the user and
// redirect back to /signin.
log.Warn(
http.StatusText(http.StatusUnauthorized)+" "+err.Error(),
echo.NewHTTPError(
http.StatusUnauthorized,
http.StatusText(http.StatusUnauthorized)+" "+err.Error(),
))
c.Redirect(302, "/signin")
return nil
}
secure := c.Request().URL.Scheme == "https"
cookieSession := &http.Cookie{
Name: "session",
Value: username,
SameSite: http.SameSiteStrictMode,
MaxAge: 3600,
Secure: secure,
HttpOnly: true,
}
c.SetCookie(cookieSession)
c.Redirect(301, "/home")
return nil
}
}
func Signup() echo.HandlerFunc {
return func(c echo.Context) error {
session, err := c.Cookie("session")
if err != nil {
if err == http.ErrNoCookie {
log.Info("no session cookie found")
}
}
if session != nil {
log.Info("got session")
// if err := session.Valid(); err == nil && session.Expires.After(time.Now()) {
if err := session.Valid(); err == nil {
c.Redirect(302, "/home")
} else {
log.Warn("invalid (or expired) session", "error", err.Error())
}
}
tpl := getTmpl("signup.tmpl")
csrf := c.Get("csrf").(string)
// secure := c.Request().URL.Scheme == "https"
// cookieCSRF := &http.Cookie{
// Name: "_csrf",
// Value: csrf,
// // SameSite: http.SameSiteStrictMode,
// SameSite: http.SameSiteLaxMode,
// MaxAge: 3600,
// Secure: secure,
// HttpOnly: true,
// }
// c.SetCookie(cookieCSRF)
err = tpl.Execute(c.Response().Writer,
page{
AppName: conf.AppName,
AppVer: appver,
Title: "Sign up",
CSRF: csrf,
DevelMode: conf.DevelMode,
Current: "signup",
},
)
if err != nil {
log.Warnf("error: %q", err)
http.Error(c.Response().Writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return nil
}
}
func SignupPost(client *ent.Client) echo.HandlerFunc {
return func(c echo.Context) error {
err := c.Request().ParseForm()
if err != nil {
return err
}
var username string
if uname := c.Request().FormValue("username"); uname != "" {
username = uname
if passwd := c.Request().FormValue("password"); passwd != "" {
ctx := context.WithValue(context.Background(), "logger", log)
if u, err := moduser.CreateUser(ctx, client, username, passwd); err != nil {
// TODO: don't return the error to the user, perhaps based
// on the devel mode.
return echo.NewHTTPError(
http.StatusInternalServerError,
http.StatusText(http.StatusInternalServerError)+" failed to create schema resources "+err.Error(),
)
} else {
log.Infof("successfully registered user '%s': %#v", username, u)
}
} else {
log.Info("user registration: password was not set, returning to /signup")
}
} else {
log.Info("user registration: username was not set, returning to /signup")
c.Redirect(302, "/signup")
return nil
}
secure := c.Request().URL.Scheme == "https"
cookieSession := &http.Cookie{
Name: "session",
Value: username,
SameSite: http.SameSiteStrictMode,
MaxAge: 3600,
Secure: secure,
HttpOnly: true,
}
c.SetCookie(cookieSession)
c.Redirect(301, "/home")
return nil
}
}
func Home() echo.HandlerFunc {
return func(c echo.Context) error {
var username string
tpl := getTmpl("home.tmpl")
session, err := c.Cookie("session")
if err != nil {
if err == http.ErrNoCookie {
// return err
log.Infof("error no cookie: %q", err)
http.Error(c.Response().Writer, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
return nil
}
log.Infof("error: %q", err)
http.Error(c.Response().Writer, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
return nil
}
if session != nil {
log.Info("got session")
// if err := session.Valid(); err == nil && session.Expires.After(time.Now()) {
if err := session.Valid(); err == nil {
username = session.Value
} else {
log.Warn("invalid or expired session?")
return echo.NewHTTPError(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
}
}
// example denial.
// if _, err := c.Cookie("aha"); err != nil {
// log.Printf("error: %q", err)
// return echo.NewHTTPError(http.StatusUnauthorized, http.StatusText(http.StatusUnauthorized))
// }
err = tpl.Execute(c.Response().Writer,
page{
AppName: conf.AppName,
AppVer: appver,
Title: "Home",
Name: username,
DevelMode: conf.DevelMode,
Current: "home",
},
)
if err != nil {
// return err
log.Warnf("error: %q", err)
return echo.NewHTTPError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
return nil
}
}
func Logout() echo.HandlerFunc {
return func(c echo.Context) error {
if c.Request().Method == "POST" {
session, err := c.Cookie("session")
if err != nil {
if errors.Is(err, http.ErrNoCookie) {
log.Info("nobody to log out, redirecting to /signin")
c.Redirect(302, "/signin")
return nil
}
log.Warnf("error: %q", err)
return nil
}
var username string
if err := session.Valid(); err == nil {
username = session.Value
}
log.Infof("logging out user '%s'", username)
secure := c.Request().URL.Scheme == "https"
cookieSession := &http.Cookie{
Name: "session",
Value: "",
SameSite: http.SameSiteStrictMode,
MaxAge: -1,
Secure: secure,
HttpOnly: true,
}
c.SetCookie(cookieSession)
}
tpl := getTmpl("logout.tmpl")
err := tpl.Execute(c.Response().Writer,
page{
AppName: conf.AppName,
AppVer: appver,
Title: "Logout",
DevelMode: conf.DevelMode,
Current: "logout",
},
)
if err != nil {
log.Warnf("error: %q", err)
return echo.NewHTTPError(http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError))
}
return nil
}
}
// experimental global redirect handler?
// http.HandleFunc("/redirect", func(w http.ResponseWriter, r *http.Request) {
// loc, err := url.QueryUnescape(r.URL.Query().Get("loc"))
// if err != nil {
// http.Error(w, fmt.Sprintf("invalid redirect: %q", r.URL.Query().Get("loc")), http.StatusBadRequest)
// return
// }
// http.Redirect(w, r, loc, http.StatusFound)
// })