password secuirty update
sophuwu sophie@skisiel.com
Sun, 18 May 2025 21:32:59 +0200
13 files changed,
134 insertions(+),
61 deletions(-)
M
animations.go
→
animations.go
@@ -3,8 +3,8 @@
import ( "crypto/md5" "encoding/base64" + "git.sophuwu.com/myweb/template" "net/http" - "sophuwu.site/myweb/template" "strings" "time" )
M
config/config.go
→
config/config.go
@@ -2,50 +2,42 @@ package config
import ( "bytes" + "log" "os" "path/filepath" - "time" + "strings" ) var ( - ListenAddr string - WebRoot string - StaticPath string - MediaPath string - Templates string - DBPath string - Email string - Name string - URL string - passHash str - passLoadTime time.Time + ListenAddr string + WebRoot string + StaticPath string + MediaPath string + Templates string + DBPath string + Email string + Name string + URL string + passHash Str + otp string ) -type str string +type Str string -func (s str) Bytes() []byte { +func (s Str) Bytes() []byte { return []byte(s) } -func (s str) String() string { +func (s Str) String() string { return string(s) +} +func OTP() string { + return otp } -func PassHash() str { - if time.Since(passLoadTime) > 5*time.Minute { - passLoad() - } +func PassHash() Str { return passHash -} - -func passLoad() { - b, err := os.ReadFile(path("userpass")) - if err != nil { - log.Fatalf("Error reading userpass: %v", err) - } - passHash = str(bytes.TrimSpace(b)) - passLoadTime = time.Now() } func path(p string) string {@@ -91,5 +83,22 @@ DBPath = path("data.db")
Email = mm["email"] Name = mm["name"] URL = mm["url"] - passLoad() + b, err := os.ReadFile(path("userpass")) + if err != nil { + log.Fatalf("Error reading userpass: %v", err) + } + bb := strings.SplitN(string(bytes.TrimSpace(b)), "\n", 2) + if len(bb) != 2 { + log.Fatalf("Error reading userpass: %v", err) + } + otp = bb[1] + otp = strings.TrimSpace(otp) + if len(otp) == 0 { + log.Fatalf("Error reading userpass: %v", err) + } + passHash = Str(strings.TrimSpace(bb[0])) + if len(passHash) == 0 { + log.Fatalf("Error reading userpass: %v", err) + } + }
M
go.mod
→
go.mod
@@ -1,12 +1,17 @@
-module sophuwu.site/myweb +module git.sophuwu.com/myweb -go 1.22.5 +go 1.24.2 require ( + git.sophuwu.com/goauth v0.0.0-20250518181224-2f42507669ae github.com/asdine/storm/v3 v3.2.1 + github.com/pquerna/otp v1.4.0 go.etcd.io/bbolt v1.3.4 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 ) -require github.com/stretchr/testify v1.7.0 // indirect +require ( + github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/stretchr/testify v1.7.0 // indirect +)
M
go.sum
→
go.sum
@@ -1,9 +1,13 @@
+git.sophuwu.com/goauth v0.0.0-20250518181224-2f42507669ae h1:ma31RY+03tqQM1dVB33VCHYFO4lrGMSHjuPJep/k2Jo= +git.sophuwu.com/goauth v0.0.0-20250518181224-2f42507669ae/go.mod h1:EmPq7U2hGF0dZVOauKkgcHXC08VjC5CtOOznXt64Jck= github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM= github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac= github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=@@ -17,8 +21,11 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
M
main.go
→
main.go
@@ -4,14 +4,16 @@ import (
"context" "errors" "fmt" + "git.sophuwu.com/goauth/htsesh" + "git.sophuwu.com/myweb/config" + "git.sophuwu.com/myweb/template" + "github.com/pquerna/otp/totp" "golang.org/x/crypto/bcrypt" "golang.org/x/sys/unix" "log" "net/http" "os" "os/signal" - "sophuwu.site/myweb/config" - "sophuwu.site/myweb/template" "strings" )@@ -84,32 +86,43 @@ // Authenticate is a middleware that checks for basic authentication.
// Passwords are hashed with bcrypt, stored in the userpass file in the // webhome directory. The file only contains one line, the bcrypt hash. // The hash is generated hashing the string "user:password" with bcrypt. -func Authenticate(next http.HandlerFunc) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - user, apass, authOK := r.BasicAuth() - if !authOK || bcrypt.CompareHashAndPassword(config.PassHash().Bytes(), []byte(user+":"+apass)) != nil { - w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) - http.Error(w, "Unauthorized.", http.StatusUnauthorized) - return - } - next.ServeHTTP(w, r) - }) -} + +// func Authenticate(next http.HandlerFunc) http.Handler { +// return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { +// user, apass, authOK := r.BasicAuth() +// if !authOK || bcrypt.CompareHashAndPassword(config.PassHash().Bytes(), []byte(user+":"+apass)) != nil { +// w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`) +// http.Error(w, "Unauthorized.", http.StatusUnauthorized) +// return +// } +// next.ServeHTTP(w, r) +// }) +// } + +var managerHandler = htsesh.Authenticate(ManagerHandler) // main is the entry point for the web server. func main() { + htsesh.SetVerifyFunc(func(username, password, otp string) bool { + return bcrypt.CompareHashAndPassword(config.PassHash().Bytes(), []byte(username+":"+password)) == nil && totp.Validate(otp, config.OTP()) + }) + OpenDB() err := template.Init(config.Templates) if err != nil { log.Fatalf("Error initializing templates: %v", err) } + d := template.Data("Management Login Only.", "Please leave this page.") + d.Set("Form", "Login") + htsesh.FORM, _ = template.FillString("manage", d) + http.HandleFunc("/", HttpIndex) http.HandleFunc("/blog/", BlogHandler) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(config.StaticPath)))) http.HandleFunc("/media/", MediaHandler) http.HandleFunc("/animations/", AnimHandler) - http.Handle("/manage/", Authenticate(ManagerHandler)) + http.Handle("/manage/", managerHandler) server := http.Server{Addr: config.ListenAddr, Handler: nil} go func() {
M
template/template.go
→
template/template.go
@@ -1,11 +1,12 @@
package template import ( + "bytes" + "git.sophuwu.com/myweb/config" "html/template" "net/http" "os" "path/filepath" - "sophuwu.site/myweb/config" ) type HTMLDataMap map[string]any@@ -34,6 +35,21 @@
var templates *template.Template var fillFunc func(w http.ResponseWriter, name string, data HTMLDataMap) error var templatesDir string + +func FillString(name string, data HTMLDataMap) (string, error) { + data.SetIfEmpty("Url", config.URL) + data.SetIfEmpty("Email", config.Email) + data.SetIfEmpty("Name", config.Name) + if data["Content"] != nil { + data["HTML"] = template.HTML(data["Content"].(string)) + } + var buf bytes.Buffer + err := templates.ExecuteTemplate(&buf, name, data) + if err != nil { + return "", err + } + return buf.String(), nil +} func parseTemplates() error { index := template.New("index")
M
webhome/static/style_dark.css
→
webhome/static/style_dark.css
@@ -18,14 +18,8 @@ }
button:active { box-shadow: none; } -textarea { - background-color: #111111; - border: 2px solid #aaaaaa; -} -input { - background-color: #111111; - border: 2px solid #aaaaaa; -} +textarea , +input , button { background-color: rgba(0, 0, 0, 0.25); border: 3px solid transparent;
M
webhome/static/style_light.css
→
webhome/static/style_light.css
@@ -18,7 +18,8 @@ }
button:active { box-shadow: none; } - +textarea , +input , button { background-color: rgba(0, 0, 0, 0.1); border: 3px solid transparent;
M
webhome/static/style_main.css
→
webhome/static/style_main.css
@@ -240,3 +240,22 @@ h3 {
margin: 0.5lh 0 0.25lh; font-size: 1.25rem; } +form { + display: flex; + flex-direction: column; + min-width: 50ch; +} +input, textarea { + margin: 0.1ch; + padding: 1ch; + border-radius: 1ch; + font-weight: 500; +} +input[type="text"], input[type="password"], textarea { + width: 50%; +} +input[type="submit"] { + cursor: pointer; + width: fit-content; + align-self: center; +}
M
webhome/templates/manage.html
→
webhome/templates/manage.html
@@ -7,8 +7,17 @@ <main>
<h2> {{ .Title }} {{ .Description }} </h2> + {{ if .Form }} + <form style="width: 50%; margin: 0 auto;" method="POST"> + <input style="width: 100%;" type="text" placeholder="username" autocomplete="off" name="username"> + <input style="width: 100%;" type="password" autocomplete="off" name="password" placeholder="password"> + <input style="width: 100%;" type="text" autocomplete="off" placeholder="otp" name="otp"> + <input type="submit" value="submit"> + </form> + {{ else }} {{ range .Options }} <a href="{{ .URL }}">{{ .Name }}</a><br> + {{ end }} {{ end }} </main> </body>