501 lines
11 KiB
Go
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)
|
|
// })
|