leo
f129606b8f
All checks were successful
continuous-integration/drone/push Build is passing
* add handlers for signin,singup,logout... * introduce ent ORM and add user schema * add live mode, devel mode to selectively turn on features via config/flags * add templates, handle embedding moar smarter: * live mode uses live folder structure, else embedded templates are used * start using tailwindcss to style stuff * add development goodies for hot-reloading (browser-sync - bs.js) * pimp-up config.dhall with actual custom config Type (enables remote schema and local values only as needed) * add justfile (alternative to makefile for process automation)
638 lines
15 KiB
Go
638 lines
15 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"html/template"
|
|
"io/fs"
|
|
"log"
|
|
"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 (
|
|
// tmpl *template.Template
|
|
tmpls = template.New("")
|
|
templateMap tplMap
|
|
tmplFS fs.FS
|
|
)
|
|
|
|
func listAllTmpls() []string {
|
|
files, err := filepath.Glob("templates/*.tmpl")
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// log.Println(files)
|
|
for i, v := range files {
|
|
files[i] = strings.TrimPrefix(v, "templates/")
|
|
}
|
|
|
|
log.Println("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 {
|
|
log.Printf("want: %s, got: %s", want, got)
|
|
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) {
|
|
// tmpls := tmpl
|
|
log.Println(tmplFS == nil)
|
|
|
|
if f != nil || f != tmplFS {
|
|
tmplFS = f
|
|
}
|
|
log.Println(tmplFS == nil)
|
|
|
|
// for _, funcs := range getFuncMap() {
|
|
// // log.Println(funcs)
|
|
// tmpls.Funcs(funcs)
|
|
// }
|
|
setFuncMap(tmpls)
|
|
|
|
// tmpls, err := tmpls.ParseFS(f, listAllTmpls()...)
|
|
// if err != nil {
|
|
// panic(err)
|
|
// }
|
|
|
|
allTmpls := listAllTmpls()
|
|
|
|
// ensure this fails at compile time, if at all ("Must").
|
|
tmpls = template.Must(tmpls.ParseFS(tmplFS, allTmpls...))
|
|
makeTplMap(tmpls)
|
|
|
|
// tp := make(tplMap, len(allTmpls))
|
|
// for _, v := range allTmpls {
|
|
// tp[v] = tmpls.Lookup(v)
|
|
// }
|
|
//
|
|
// templateMap = tp
|
|
|
|
// tmpl = 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 {
|
|
// reload the requested template before returning it.
|
|
// tmpls = template.Must(tmpls.ParseFS(tmplFS, name))
|
|
// nuTmpls, err := tmpls.ParseFS(tmplFS, name)
|
|
// nuTmpls, err := tmpls.ParseFS(tmplFS, name)
|
|
|
|
//if err != nil {
|
|
// log.Printf("error refreshing tmpl %s, '%q'", name, err)
|
|
//} else {
|
|
// tmpls = nuTmpls
|
|
// makeTplMap(tmpls)
|
|
//}
|
|
|
|
// tpl := template.Must(template.New(name).ParseFS(tmplFS, name))
|
|
// templateMap[name] = tpl
|
|
// return tpl
|
|
log.Println("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...))
|
|
// InitTemplates(tmplFS)
|
|
}
|
|
|
|
return tmpls.Lookup(name)
|
|
}
|
|
|
|
func Admin() echo.HandlerFunc {
|
|
return func(c echo.Context) error {
|
|
return echo.NewHTTPError(http.StatusUnauthorized, "Invalid credentials")
|
|
}
|
|
}
|
|
|
|
// func Index(conf *config.Config) echo.HandlerFunc {
|
|
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 = tpl.Funcs(template.FuncMap{
|
|
// "pageIs": func(want, got string) bool {
|
|
// if want == got {
|
|
// return true
|
|
// }
|
|
// return false
|
|
// },
|
|
// })
|
|
// log.Printf("%v", tpl)
|
|
|
|
// tpl := templateMap["index.tmpl"]
|
|
|
|
// secure := c.Request().URL.Scheme == "https"
|
|
// secure := true
|
|
csrf := c.Get("csrf").(string)
|
|
// 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,
|
|
// struct {
|
|
// AppName string
|
|
// AppVer string
|
|
// Title string
|
|
// CSRF string
|
|
// DevelMode bool
|
|
// }{
|
|
// AppName: conf.AppName,
|
|
// AppVer: appver,
|
|
// Title: "Welcome!",
|
|
// CSRF: csrf,
|
|
// DevelMode: conf.DevelMode,
|
|
// },
|
|
page{
|
|
AppName: conf.AppName,
|
|
AppVer: appver,
|
|
Title: "Welcome!",
|
|
CSRF: csrf,
|
|
DevelMode: conf.DevelMode,
|
|
Current: "home",
|
|
},
|
|
)
|
|
if err != nil {
|
|
log.Printf("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.Println("no session cookie found")
|
|
}
|
|
}
|
|
if session != nil {
|
|
log.Println("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.Println("invalid (or expired) session")
|
|
log.Println("error:", err)
|
|
}
|
|
}
|
|
|
|
tpl := getTmpl("signin.tmpl")
|
|
// secure := c.Request().URL.Scheme == "https"
|
|
// secure := true
|
|
// csrf := c.Get("csrf").(string)
|
|
// 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,
|
|
// struct {
|
|
// AppName string
|
|
// AppVer string
|
|
// Title string
|
|
// DevelMode bool
|
|
// }{
|
|
// AppName: conf.AppName,
|
|
// AppVer: appver,
|
|
// Title: "Sign in",
|
|
// DevelMode: conf.DevelMode,
|
|
// },
|
|
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.Printf("authenticating user '%s' at /signin", username)
|
|
} else {
|
|
log.Printf("username was not set, returning to /signin")
|
|
|
|
c.Redirect(302, "/signin")
|
|
|
|
return nil
|
|
}
|
|
if passwd := c.Request().FormValue("password"); passwd != "" {
|
|
password = passwd
|
|
} else {
|
|
log.Printf("password was not set, returning to /signin")
|
|
|
|
c.Redirect(302, "/signin")
|
|
|
|
return nil
|
|
}
|
|
|
|
// if usr := client.User.Query().
|
|
// Where(
|
|
// user.Username(username),
|
|
// ); usr != nil {
|
|
if usr, err := moduser.QueryUser(context.Background(), client, username); err == nil {
|
|
log.Println(usr)
|
|
if usr.Password != password {
|
|
log.Println("wrong user credentials, redirecting to /signin")
|
|
|
|
c.Redirect(302, "/signin")
|
|
}
|
|
// if user.Password(password) {
|
|
// c.Redirect(302, "/signin")
|
|
// }
|
|
|
|
// if err != nil || p != password {
|
|
// log.Println(echo.NewHTTPError(
|
|
// http.StatusUnauthorized,
|
|
// http.StatusText(http.StatusUnauthorized)+" "+err.Error(),
|
|
// ))
|
|
//
|
|
// c.Redirect(302, "/signin")
|
|
// return nil
|
|
// }
|
|
} else {
|
|
// just log the error instead of returning it to the user and
|
|
// redirect back to /signin.
|
|
log.Println(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,
|
|
SameSite: http.SameSiteLaxMode,
|
|
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.Println("no session cookie found")
|
|
}
|
|
}
|
|
if session != nil {
|
|
log.Println("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.Println("invalid (or expired) session")
|
|
log.Println("error:", err)
|
|
}
|
|
}
|
|
|
|
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.Printf("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 {
|
|
// tpl := getTmpl("signup.tmpl", conf.LiveMode)
|
|
|
|
ct := c.Request().Header.Get("Content-Type")
|
|
fmt.Printf("content-type: %s\n", ct)
|
|
|
|
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 != "" {
|
|
if u, err := moduser.CreateUser(context.Background(), 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.Printf("successfully registered user '%s': %#v", username, u)
|
|
}
|
|
} else {
|
|
log.Println("password was not set, returning to /signup")
|
|
}
|
|
} else {
|
|
log.Printf("username was not set, returning to /signup")
|
|
|
|
c.Redirect(302, "/signup")
|
|
|
|
return nil
|
|
}
|
|
|
|
secure := c.Request().URL.Scheme == "https"
|
|
// secure := true
|
|
// csrf := c.Get("csrf").(string)
|
|
|
|
cookieSession := &http.Cookie{
|
|
Name: "session",
|
|
Value: username,
|
|
// SameSite: http.SameSiteStrictMode,
|
|
SameSite: http.SameSiteLaxMode,
|
|
MaxAge: 3600,
|
|
Secure: secure,
|
|
HttpOnly: true,
|
|
}
|
|
c.SetCookie(cookieSession)
|
|
|
|
// cookieCSRF := &http.Cookie{
|
|
// Name: "_csrf",
|
|
// Value: csrf,
|
|
// // SameSite: http.SameSiteStrictMode,
|
|
// SameSite: http.SameSiteLaxMode,
|
|
// MaxAge: 3600,
|
|
// Secure: secure,
|
|
// HttpOnly: true,
|
|
// }
|
|
// c.SetCookie(cookieCSRF)
|
|
|
|
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.Printf("error no cookie: %q", err)
|
|
http.Error(c.Response().Writer, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
|
|
return nil
|
|
}
|
|
|
|
log.Println("error:", err)
|
|
http.Error(c.Response().Writer, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
|
// fmt.Println("session cookie not found, redirecting to signin page")
|
|
|
|
return nil
|
|
|
|
}
|
|
if session != nil {
|
|
log.Println("got session")
|
|
// if err := session.Valid(); err == nil && session.Expires.After(time.Now()) {
|
|
if err := session.Valid(); err == nil {
|
|
username = session.Value
|
|
} else {
|
|
log.Println("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.Printf("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) {
|
|
fmt.Println("nobody to log out, redirecting to /signin")
|
|
|
|
c.Redirect(302, "/signin")
|
|
|
|
return nil
|
|
}
|
|
|
|
log.Printf("error: %q", err)
|
|
return nil
|
|
}
|
|
|
|
var username string
|
|
if err := session.Valid(); err == nil {
|
|
username = session.Value
|
|
}
|
|
|
|
log.Printf("logging out user '%s'", username)
|
|
|
|
secure := c.Request().URL.Scheme == "https"
|
|
cookieSession := &http.Cookie{
|
|
Name: "session",
|
|
Value: "",
|
|
// SameSite: http.SameSiteStrictMode,
|
|
SameSite: http.SameSiteLaxMode,
|
|
MaxAge: -1,
|
|
Secure: secure,
|
|
HttpOnly: true,
|
|
}
|
|
c.SetCookie(cookieSession)
|
|
}
|
|
|
|
tpl := getTmpl("logout.tmpl")
|
|
|
|
err := tpl.Execute(c.Response().Writer,
|
|
struct {
|
|
AppName string
|
|
AppVer string
|
|
Title string
|
|
DevelMode bool
|
|
}{
|
|
AppName: conf.AppName,
|
|
AppVer: appver,
|
|
Title: "Logout",
|
|
DevelMode: conf.DevelMode,
|
|
},
|
|
)
|
|
if err != nil {
|
|
log.Printf("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)
|
|
// })
|