diff --git a/tools/srv/assets/templates/base.html.twig b/tools/srv/assets/templates/base.gohtml
similarity index 76%
rename from tools/srv/assets/templates/base.html.twig
rename to tools/srv/assets/templates/base.gohtml
index 9740375..e811482 100644
--- a/tools/srv/assets/templates/base.html.twig
+++ b/tools/srv/assets/templates/base.gohtml
@@ -1,3 +1,4 @@
+{{ define "base" }}
@@ -5,11 +6,11 @@
2fa demo
-
+
- {% block body %}{% endblock %}
+
+ {{ template "body" . }}
+
+{{ end }}
diff --git a/tools/srv/assets/templates/blocks/flash.gohtml b/tools/srv/assets/templates/blocks/flash.gohtml
new file mode 100644
index 0000000..df7cefd
--- /dev/null
+++ b/tools/srv/assets/templates/blocks/flash.gohtml
@@ -0,0 +1,7 @@
+{{ define "flash" }}
+ {{ if .flash}}
+
+ {{ .flash.Message }}
+
+ {{ end }}
+{{ end }}
diff --git a/tools/srv/assets/templates/blocks/flash.html.twig b/tools/srv/assets/templates/blocks/flash.html.twig
deleted file mode 100644
index ec6548a..0000000
--- a/tools/srv/assets/templates/blocks/flash.html.twig
+++ /dev/null
@@ -1 +0,0 @@
-{% if flash.Message %} {{ flash.Message }}
{% endif %}
diff --git a/tools/srv/assets/templates/pages/2fa-create.html.twig b/tools/srv/assets/templates/pages/2fa-create.gohtml
similarity index 50%
rename from tools/srv/assets/templates/pages/2fa-create.html.twig
rename to tools/srv/assets/templates/pages/2fa-create.gohtml
index eeae583..767cae8 100644
--- a/tools/srv/assets/templates/pages/2fa-create.html.twig
+++ b/tools/srv/assets/templates/pages/2fa-create.gohtml
@@ -1,16 +1,13 @@
-{% extends "base.html.twig" %}
-
-{% block body %}
+{{ define "body" }}
Create OTP
-{% include "blocks/flash.html.twig" %}
+{{ template "flash" . }}
-
-{% endblock %}
+{{ end }}
diff --git a/tools/srv/assets/templates/pages/2fa.html.twig b/tools/srv/assets/templates/pages/2fa.gohtml
similarity index 65%
rename from tools/srv/assets/templates/pages/2fa.html.twig
rename to tools/srv/assets/templates/pages/2fa.gohtml
index 79b5dda..f6ab007 100644
--- a/tools/srv/assets/templates/pages/2fa.html.twig
+++ b/tools/srv/assets/templates/pages/2fa.gohtml
@@ -1,14 +1,12 @@
-{% extends "base.html.twig" %}
-
-{% block body %}
+{{ define "body" }}
Token
-{% include "blocks/flash.html.twig" %}
+{{ template "flash" . }}
-{% endblock %}
+{{ end }}
diff --git a/tools/srv/assets/templates/pages/login.html.twig b/tools/srv/assets/templates/pages/login.gohtml
similarity index 76%
rename from tools/srv/assets/templates/pages/login.html.twig
rename to tools/srv/assets/templates/pages/login.gohtml
index a00feb5..a360c8c 100644
--- a/tools/srv/assets/templates/pages/login.html.twig
+++ b/tools/srv/assets/templates/pages/login.gohtml
@@ -1,10 +1,8 @@
-{% extends "base.html.twig" %}
-
-{% block body %}
+{{ define "body" }}
Login
-{% include "blocks/flash.html.twig" %}
+{{ template "flash" . }}
-{% endblock %}
+{{ end }}
diff --git a/tools/srv/assets/templates/pages/register.html.twig b/tools/srv/assets/templates/pages/register.gohtml
similarity index 76%
rename from tools/srv/assets/templates/pages/register.html.twig
rename to tools/srv/assets/templates/pages/register.gohtml
index a05b895..7a4a115 100644
--- a/tools/srv/assets/templates/pages/register.html.twig
+++ b/tools/srv/assets/templates/pages/register.gohtml
@@ -1,10 +1,8 @@
-{% extends "base.html.twig" %}
-
-{% block body %}
+{{ define "body" }}
Register account
-{% include "blocks/flash.html.twig" %}
+{{ template "flash" . }}
-{% endblock %}
+{{ end }}
diff --git a/tools/srv/assets/templates/pages/user.gohtml b/tools/srv/assets/templates/pages/user.gohtml
new file mode 100644
index 0000000..f8167e0
--- /dev/null
+++ b/tools/srv/assets/templates/pages/user.gohtml
@@ -0,0 +1,6 @@
+{{ define "body" }}
+ User area
+ {{ template "flash" . }}
+ Hello {{ .user.Username }}!
+ Logout
+{{ end }}
diff --git a/tools/srv/assets/templates/pages/user.html.twig b/tools/srv/assets/templates/pages/user.html.twig
deleted file mode 100644
index 83e8a38..0000000
--- a/tools/srv/assets/templates/pages/user.html.twig
+++ /dev/null
@@ -1,8 +0,0 @@
-{% extends "base.html.twig" %}
-
-{% block body %}
- User area
- {% include "blocks/flash.html.twig" %}
- Hello {{ user.Username }}!
- Logout
-{% endblock %}
diff --git a/tools/srv/go.mod b/tools/srv/go.mod
index 64f9cfe..8688979 100644
--- a/tools/srv/go.mod
+++ b/tools/srv/go.mod
@@ -6,11 +6,7 @@ require (
github.com/alexedwards/scs/v2 v2.9.0
github.com/go-chi/chi/v5 v5.3.1
github.com/pquerna/otp v1.5.0
- github.com/tyler-sommer/stick v1.0.10
golang.org/x/crypto v0.54.0
)
-require (
- github.com/boombuler/barcode v1.1.0 // indirect
- github.com/shopspring/decimal v1.4.0 // indirect
-)
+require github.com/boombuler/barcode v1.1.0 // indirect
diff --git a/tools/srv/go.sum b/tools/srv/go.sum
index 5eccdc2..0152f13 100644
--- a/tools/srv/go.sum
+++ b/tools/srv/go.sum
@@ -1,44 +1,18 @@
-github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
-github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
github.com/alexedwards/scs/v2 v2.9.0 h1:xa05mVpwTBm1iLeTMNFfAWpKUm4fXAW7CeAViqBVS90=
github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
-github.com/boombuler/barcode v1.0.2 h1:79yrbttoZrLGkL/oOI8hBrUKucwOL0oOjUgEguGMcJ4=
-github.com/boombuler/barcode v1.0.2/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/go-chi/chi/v5 v5.2.0 h1:Aj1EtB0qR2Rdo2dG4O94RIU35w2lvQSj6BRA4+qwFL0=
-github.com/go-chi/chi/v5 v5.2.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=
-github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
-github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
-github.com/go-chi/chi/v5 v5.3.0 h1:halUjDxhshgXHMrao5bB8eNBXo/rnzwr8m5m36glehM=
-github.com/go-chi/chi/v5 v5.3.0/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/go-chi/chi/v5 v5.3.1 h1:3j4HZLGZQ3JpMCrPJF/Jl3mYJfWLKBfNJ6quurUGCf8=
github.com/go-chi/chi/v5 v5.3.1/go.mod h1:R+tYY2hNuVUUjxoPtqUdgBqevM9s9njzkTLutVsOCto=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
-github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/pquerna/otp v1.5.0 h1:NMMR+WrmaqXU4EzdGJEE1aUUI0AMRzsp96fFFWNPwxs=
github.com/pquerna/otp v1.5.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
-github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
-github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
-github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
-github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/tyler-sommer/stick v1.0.6 h1:LLdJ8oGotXCsAuVx2BRZZT6s3bYXHe0ImQ+azF4HtJg=
-github.com/tyler-sommer/stick v1.0.6/go.mod h1:rjBy3zi6GwoxExa6OSRPPPaLqUEKNsBxTeWckhIX1us=
-github.com/tyler-sommer/stick v1.0.10 h1:VlXTvaxXnOtfkoTxqy+FWRM0OdN7sGX8Nfh8t2I1GSI=
-github.com/tyler-sommer/stick v1.0.10/go.mod h1:CYdsPuvxGEU32yi4ZTDul+55R8oS8/aHuWSppEq01B8=
-golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
-golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
-golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
-golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
-golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
golang.org/x/crypto v0.54.0 h1:YLIA59K4fiNzHzjnZt2tUJQjQtUWfWbeHBqKtk3eScw=
golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk=
diff --git a/tools/srv/main.go b/tools/srv/main.go
index f4a65e5..b3431db 100644
--- a/tools/srv/main.go
+++ b/tools/srv/main.go
@@ -2,9 +2,12 @@ package main
import (
"bytes"
+ "embed"
+ "encoding/base32"
"encoding/base64"
"encoding/gob"
"fmt"
+ "html/template"
"image/png"
"log"
"net/http"
@@ -17,7 +20,6 @@ import (
"github.com/go-chi/chi/v5/middleware"
"github.com/pquerna/otp"
"github.com/pquerna/otp/totp"
- "github.com/tyler-sommer/stick"
"golang.org/x/crypto/bcrypt"
)
@@ -39,7 +41,7 @@ type (
flash struct{ Message, Type string }
- m map[string]stick.Value
+ m map[string]any
db struct {
*sync.Mutex
@@ -48,11 +50,31 @@ type (
)
var (
- env = stick.New(stick.NewFilesystemLoader("./assets/templates"))
+ //go:embed assets/templates assets/css
+ assets embed.FS
+ pages = map[string]*template.Template{}
store = &db{new(sync.Mutex), make(map[string]*user)}
session = scs.New()
)
+func init() {
+ for _, p := range []string{"login", "register", "2fa", "2fa-create", "user"} {
+ pages[p] = template.Must(template.ParseFS(
+ assets,
+ "assets/templates/base.gohtml",
+ "assets/templates/blocks/flash.gohtml",
+ "assets/templates/pages/"+p+".gohtml",
+ ))
+ }
+}
+
+func render(w http.ResponseWriter, page string, data m) {
+ if err := pages[page].ExecuteTemplate(w, "base", data); err != nil {
+ log.Println("render:", err)
+ http.Error(w, "internal error", 500)
+ }
+}
+
/// app
func main() {
@@ -90,8 +112,7 @@ func mux() chi.Router {
r.Get("/logout", getLogout)
})
- fs := http.FileServer(http.Dir("./assets/css"))
- mux.Handle("/assets/*", http.StripPrefix("/assets", fs))
+ mux.Handle("/assets/*", http.FileServerFS(assets))
return mux
}
@@ -112,11 +133,10 @@ func getLogin(w http.ResponseWriter, r *http.Request) {
}
flash, _ := session.Pop(r.Context(), "flash").(*flash)
- env.Execute("pages/login.html.twig", w, m{"flash": flash})
+ render(w, "login", m{"flash": flash})
}
func postLogin(w http.ResponseWriter, r *http.Request) {
-
u, err := store.Find(r.PostFormValue("username"))
if err != nil {
log.Println(err)
@@ -143,7 +163,7 @@ func postLogin(w http.ResponseWriter, r *http.Request) {
func getRegister(w http.ResponseWriter, r *http.Request) {
flash, _ := session.Pop(r.Context(), "flash").(*flash)
- env.Execute("pages/register.html.twig", w, m{"flash": flash})
+ render(w, "register", m{"flash": flash})
}
func postRegister(w http.ResponseWriter, r *http.Request) {
@@ -189,15 +209,27 @@ func createOTP(w http.ResponseWriter, r *http.Request) {
return
}
+ var secret []byte
+ if u.Secret != "" {
+ var err error
+ if secret, err = base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(u.Secret); err != nil {
+ log.Println("otp:", err)
+ session.Put(r.Context(), "flash", &flash{err.Error(), "danger"})
+ http.Redirect(w, r, "/otp/new", http.StatusFound)
+ return
+ }
+ }
+
opts := totp.GenerateOpts{
Issuer: issuer,
Algorithm: otpAlg,
AccountName: u.Username,
SecretSize: otpSecretSize,
Digits: otp.DigitsSix,
+ Secret: secret,
}
- otp, err := totp.Generate(opts)
+ key, err := totp.Generate(opts)
if err != nil {
log.Println("otp:", err)
session.Put(r.Context(), "flash", &flash{err.Error(), "danger"})
@@ -205,7 +237,8 @@ func createOTP(w http.ResponseWriter, r *http.Request) {
return
}
- u.Secret = otp.Secret()
+ u.Secret = key.Secret()
+
if err := store.Update(u); err != nil {
log.Println(err)
session.Put(r.Context(), "flash", &flash{err.Error(), "danger"})
@@ -214,7 +247,7 @@ func createOTP(w http.ResponseWriter, r *http.Request) {
}
buf := new(bytes.Buffer)
- qr, err := otp.Image(240, 240)
+ qr, err := key.Image(240, 240)
if err != nil {
session.Put(r.Context(), "flash", &flash{err.Error(), "danger"})
http.Redirect(w, r, "/", http.StatusFound)
@@ -228,8 +261,10 @@ func createOTP(w http.ResponseWriter, r *http.Request) {
}
flash, _ := session.Pop(r.Context(), "flash").(*flash)
- env.Execute("pages/2fa-create.html.twig", w, m{
- "qr": base64.RawStdEncoding.EncodeToString(buf.Bytes()),
+ imgBytes := base64.RawStdEncoding.EncodeToString(buf.Bytes())
+
+ render(w, "2fa-create", m{
+ "qr": template.URL("data:image/png;base64," + imgBytes),
"flash": flash,
})
}
@@ -241,7 +276,8 @@ func getOTP(w http.ResponseWriter, r *http.Request) {
}
flash, _ := session.Pop(r.Context(), "flash").(*flash)
- env.Execute("pages/2fa.html.twig", w, m{"flash": flash})
+
+ render(w, "2fa", m{"flash": flash})
}
func postOTP(w http.ResponseWriter, r *http.Request) {
@@ -255,7 +291,13 @@ func postOTP(w http.ResponseWriter, r *http.Request) {
return
}
- opts := totp.ValidateOpts{Algorithm: otpAlg, Digits: otpLen}
+ opts := totp.ValidateOpts{
+ Algorithm: otpAlg,
+ Digits: otpLen,
+ Period: 30,
+ Skew: 1,
+ }
+
if valid, err := totp.ValidateCustom(token, user.Secret, time.Now().UTC(), opts); !valid || err != nil {
if err != nil {
log.Println("otp: validate:", err)
@@ -274,13 +316,14 @@ func postOTP(w http.ResponseWriter, r *http.Request) {
}
session.Put(r.Context(), "otp-validated", true)
+ session.Put(r.Context(), "user", user)
http.Redirect(w, r, "/user", http.StatusFound)
}
func getUser(w http.ResponseWriter, r *http.Request) {
u, _ := session.Get(r.Context(), "user").(*user)
- env.Execute("pages/user.html.twig", w, m{"user": u})
+ render(w, "user", m{"user": u})
}
func getLogout(w http.ResponseWriter, r *http.Request) {