QR codes complete, account settings finished!

+ refactored templates a little; this might need more work!
This commit is contained in:
ari melody 2025-01-26 20:09:18 +00:00
parent 1edc051ae2
commit 3450d879ac
Signed by: ari
GPG key ID: CF99829C92678188
13 changed files with 175 additions and 135 deletions

View file

@ -63,7 +63,7 @@ func accountIndexHandler(app *model.AppState) http.Handler {
session.Message = sessionMessage
session.Error = sessionError
err = pages["account"].Execute(w, accountResponse{
err = accountTemplate.Execute(w, accountResponse{
Session: session,
TOTPs: totps,
})
@ -199,7 +199,7 @@ func totpSetupHandler(app *model.AppState) http.Handler {
session := r.Context().Value("session").(*model.Session)
err := pages["totp-setup"].Execute(w, totpSetupData{ Session: session })
err := totpSetupTemplate.Execute(w, totpSetupData{ Session: session })
if err != nil {
fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -216,6 +216,7 @@ func totpSetupHandler(app *model.AppState) http.Handler {
Session *model.Session
TOTP *model.TOTP
NameEscaped string
QRBase64Image string
}
err := r.ParseForm()
@ -242,7 +243,7 @@ func totpSetupHandler(app *model.AppState) http.Handler {
if err != nil {
fmt.Printf("WARN: Failed to create TOTP method: %s\n", err)
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
err := pages["totp-setup"].Execute(w, totpSetupData{ Session: session })
err := totpSetupTemplate.Execute(w, totpSetupData{ Session: session })
if err != nil {
fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -250,10 +251,24 @@ func totpSetupHandler(app *model.AppState) http.Handler {
return
}
err = pages["totp-confirm"].Execute(w, totpSetupData{
qrBase64Image, err := controller.GenerateQRCode(
controller.GenerateTOTPURI(session.Account.Username, totp.Secret))
if err != nil {
fmt.Printf("WARN: Failed to generate TOTP setup QR code: %s\n", err)
controller.SetSessionError(app.DB, session, "Something went wrong. Please try again.")
err := totpSetupTemplate.Execute(w, totpSetupData{ Session: session })
if err != nil {
fmt.Printf("WARN: Failed to render TOTP setup page: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
return
}
err = totpConfirmTemplate.Execute(w, totpSetupData{
Session: session,
TOTP: &totp,
NameEscaped: url.PathEscape(totp.Name),
QRBase64Image: qrBase64Image,
})
if err != nil {
fmt.Printf("WARN: Failed to render TOTP confirm page: %s\n", err)
@ -304,18 +319,12 @@ func totpConfirmHandler(app *model.AppState) http.Handler {
return
}
fmt.Printf(
"TOTP:\n\tName: %s\n\tSecret: %s\n",
totp.Name,
totp.Secret,
)
confirmCode := controller.GenerateTOTP(totp.Secret, 0)
if code != confirmCode {
confirmCodeOffset := controller.GenerateTOTP(totp.Secret, 1)
if code != confirmCodeOffset {
controller.SetSessionError(app.DB, session, "Incorrect TOTP code. Please try again.")
err = pages["totp-confirm"].Execute(w, totpConfirmData{
err = totpConfirmTemplate.Execute(w, totpConfirmData{
Session: session,
TOTP: totp,
})

View file

@ -39,7 +39,7 @@ func serveArtist(app *model.AppState) http.Handler {
session := r.Context().Value("session").(*model.Session)
err = pages["artist"].Execute(w, ArtistResponse{
err = artistTemplate.Execute(w, ArtistResponse{
Session: session,
Artist: artist,
Credits: credits,

View file

@ -20,7 +20,7 @@ func Handler(app *model.AppState) http.Handler {
mux := http.NewServeMux()
mux.Handle("/qr-test", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
qrB64Img, err := controller.GenerateQRCode([]byte("super epic mega gaming test message. be sure to buy free2play on bandcamp so i can put food on my family"))
qrB64Img, err := controller.GenerateQRCode("super epic mega gaming test message. be sure to buy free2play on bandcamp so i can put food on my family")
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to generate QR code: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -87,7 +87,7 @@ func AdminIndexHandler(app *model.AppState) http.Handler {
Tracks []*model.Track
}
err = pages["index"].Execute(w, IndexData{
err = indexTemplate.Execute(w, IndexData{
Session: session,
Releases: releases,
Artists: artists,
@ -116,7 +116,7 @@ func registerAccountHandler(app *model.AppState) http.Handler {
}
render := func() {
err := pages["register"].Execute(w, registerData{ Session: session })
err := registerTemplate.Execute(w, registerData{ Session: session })
if err != nil {
fmt.Printf("WARN: Error rendering create account page: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -229,7 +229,7 @@ func loginHandler(app *model.AppState) http.Handler {
}
render := func() {
err := pages["login"].Execute(w, loginData{ Session: session })
err := loginTemplate.Execute(w, loginData{ Session: session })
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Error rendering admin login page: %s\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -307,7 +307,7 @@ func loginHandler(app *model.AppState) http.Handler {
Username string
Password string
}
err = pages["login-totp"].Execute(w, loginTOTPData{
err = loginTOTPTemplate.Execute(w, loginTOTPData{
Session: session,
Username: credentials.Username,
Password: credentials.Password,
@ -379,7 +379,7 @@ func logoutHandler(app *model.AppState) http.Handler {
Path: "/",
})
err = pages["logout"].Execute(w, nil)
err = logoutTemplate.Execute(w, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "WARN: Failed to render logout page: %v\n", err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)

View file

@ -60,7 +60,7 @@ func serveRelease(app *model.AppState) http.Handler {
Release *model.Release
}
err = pages["release"].Execute(w, ReleaseResponse{
err = releaseTemplate.Execute(w, ReleaseResponse{
Session: session,
Release: release,
})
@ -74,7 +74,7 @@ func serveRelease(app *model.AppState) http.Handler {
func serveEditCredits(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
err := components["editcredits"].Execute(w, release)
err := editCreditsTemplate.Execute(w, release)
if err != nil {
fmt.Printf("Error rendering edit credits component for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -97,7 +97,7 @@ func serveAddCredit(app *model.AppState, release *model.Release) http.Handler {
}
w.Header().Set("Content-Type", "text/html")
err = components["addcredit"].Execute(w, response{
err = addCreditTemplate.Execute(w, response{
ReleaseID: release.ID,
Artists: artists,
})
@ -123,7 +123,7 @@ func serveNewCredit(app *model.AppState) http.Handler {
}
w.Header().Set("Content-Type", "text/html")
err = components["newcredit"].Execute(w, artist)
err = newCreditTemplate.Execute(w, artist)
if err != nil {
fmt.Printf("Error rendering new credit component for %s: %s\n", artist.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -134,7 +134,7 @@ func serveNewCredit(app *model.AppState) http.Handler {
func serveEditLinks(release *model.Release) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
err := components["editlinks"].Execute(w, release)
err := editLinksTemplate.Execute(w, release)
if err != nil {
fmt.Printf("Error rendering edit links component for %s: %s\n", release.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
@ -151,7 +151,7 @@ func serveEditTracks(release *model.Release) http.Handler {
Add func(a int, b int) int
}
err := components["edittracks"].Execute(w, editTracksData{
err := editTracksTemplate.Execute(w, editTracksData{
Release: release,
Add: func(a, b int) int { return a + b },
})
@ -177,7 +177,7 @@ func serveAddTrack(app *model.AppState, release *model.Release) http.Handler {
}
w.Header().Set("Content-Type", "text/html")
err = components["addtrack"].Execute(w, response{
err = addTrackTemplate.Execute(w, response{
ReleaseID: release.ID,
Tracks: tracks,
})
@ -204,7 +204,7 @@ func serveNewTrack(app *model.AppState) http.Handler {
}
w.Header().Set("Content-Type", "text/html")
err = components["newtrack"].Execute(w, track)
err = newTrackTemplate.Execute(w, track)
if err != nil {
fmt.Printf("Error rendering new track component for %s: %s\n", track.ID, err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)

View file

@ -1,80 +1,90 @@
package admin
import (
"html/template"
"path/filepath"
"html/template"
"path/filepath"
)
var pages = map[string]*template.Template{
"index": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "components", "release", "release-list-item.html"),
filepath.Join("admin", "views", "index.html"),
)),
var indexTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "components", "release", "release-list-item.html"),
filepath.Join("admin", "views", "index.html"),
))
"login": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "login.html"),
)),
"login-totp": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "login-totp.html"),
)),
"register": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "register.html"),
)),
"logout": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "logout.html"),
)),
"account": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "edit-account.html"),
)),
"totp-setup": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "totp-setup.html"),
)),
"totp-confirm": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "totp-confirm.html"),
)),
var loginTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "login.html"),
))
var loginTOTPTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "login-totp.html"),
))
var registerTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "register.html"),
))
var logoutTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "logout.html"),
))
var accountTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "edit-account.html"),
))
var totpSetupTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "totp-setup.html"),
))
var totpConfirmTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "totp-confirm.html"),
))
"release": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "edit-release.html"),
)),
"artist": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "edit-artist.html"),
)),
"track": template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "components", "release", "release-list-item.html"),
filepath.Join("admin", "views", "edit-track.html"),
)),
}
var releaseTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "edit-release.html"),
))
var artistTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "views", "edit-artist.html"),
))
var trackTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "views", "layout.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("admin", "components", "release", "release-list-item.html"),
filepath.Join("admin", "views", "edit-track.html"),
))
var components = map[string]*template.Template{
"editcredits": template.Must(template.ParseFiles(filepath.Join("admin", "components", "credits", "editcredits.html"))),
"addcredit": template.Must(template.ParseFiles(filepath.Join("admin", "components", "credits", "addcredit.html"))),
"newcredit": template.Must(template.ParseFiles(filepath.Join("admin", "components", "credits", "newcredit.html"))),
var editCreditsTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "credits", "editcredits.html"),
))
var addCreditTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "credits", "addcredit.html"),
))
var newCreditTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "credits", "newcredit.html"),
))
"editlinks": template.Must(template.ParseFiles(filepath.Join("admin", "components", "links", "editlinks.html"))),
var editLinksTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "links", "editlinks.html"),
))
"edittracks": template.Must(template.ParseFiles(filepath.Join("admin", "components", "tracks", "edittracks.html"))),
"addtrack": template.Must(template.ParseFiles(filepath.Join("admin", "components", "tracks", "addtrack.html"))),
"newtrack": template.Must(template.ParseFiles(filepath.Join("admin", "components", "tracks", "newtrack.html"))),
}
var editTracksTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "tracks", "edittracks.html"),
))
var addTrackTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "tracks", "addtrack.html"),
))
var newTrackTemplate = template.Must(template.ParseFiles(
filepath.Join("admin", "components", "tracks", "newtrack.html"),
))

View file

@ -39,7 +39,7 @@ func serveTrack(app *model.AppState) http.Handler {
session := r.Context().Value("session").(*model.Session)
err = pages["track"].Execute(w, TrackResponse{
err = trackTemplate.Execute(w, TrackResponse{
Session: session,
Track: track,
Releases: releases,

View file

@ -3,6 +3,9 @@
<link rel="shortcut icon" href="/img/favicon.png" type="image/x-icon">
<link rel="stylesheet" href="/admin/static/admin.css">
<style>
.qr-code {
border: 1px solid #8888;
}
code {
user-select: all;
}
@ -16,15 +19,19 @@ code {
{{end}}
<form action="/admin/account/totp-confirm?totp-name={{.NameEscaped}}" method="POST" id="totp-setup">
<p><strong>Your TOTP secret: </strong><code>{{.TOTP.Secret}}</code></p>
<!-- TODO: TOTP secret QR codes -->
<img src="data:image/png;base64,{{.QRBase64Image}}" alt="" class="qr-code">
<p>
Please store this into your two-factor authentication app or
password manager, then enter your code below:
Scan the QR code above into your authentication app or password manager,
then enter your 2FA code below.
</p>
<p>
If the QR code does not work, you may also enter this secret code:
</p>
<p><code>{{.TOTP.Secret}}</code></p>
<label for="totp">TOTP:</label>
<input type="text" name="totp" value="" autocomplete="one-time-code" required autofocus>

View file

@ -8,8 +8,21 @@ import (
"image"
"image/color"
"image/png"
"github.com/skip2/go-qrcode"
)
func GenerateQRCode(data string) (string, error) {
imgBytes, err := qrcode.Encode(data, qrcode.Medium, 256)
if err != nil {
return "", err
}
base64Img := base64.StdEncoding.EncodeToString(imgBytes)
return base64Img, nil
}
// vvv DEPRECATED vvv
const margin = 4
type QRCodeECCLevel int64
@ -20,7 +33,7 @@ const (
HIGH
)
func GenerateQRCode(data []byte) (string, error) {
func noDepsGenerateQRCode() (string, error) {
version := 1
size := 0

6
go.mod
View file

@ -8,4 +8,8 @@ require (
)
require golang.org/x/crypto v0.27.0 // indirect
require github.com/pelletier/go-toml/v2 v2.2.3 // indirect
require (
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
)

6
go.sum
View file

@ -8,7 +8,9 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=

View file

@ -404,7 +404,7 @@ func createServeMux(app *model.AppState) *http.ServeMux {
}
if r.URL.Path == "/" || r.URL.Path == "/index.html" {
err := templates.Pages["index"].Execute(w, nil)
err := templates.IndexTemplate.Execute(w, nil)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}

View file

@ -5,29 +5,24 @@ import (
"path/filepath"
)
var Pages = map[string]*template.Template{
"index": template.Must(template.ParseFiles(
filepath.Join("views", "layout.html"),
filepath.Join("views", "header.html"),
filepath.Join("views", "footer.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("views", "index.html"),
)),
"music": template.Must(template.ParseFiles(
filepath.Join("views", "layout.html"),
filepath.Join("views", "header.html"),
filepath.Join("views", "footer.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("views", "music.html"),
)),
"music-gateway": template.Must(template.ParseFiles(
filepath.Join("views", "layout.html"),
filepath.Join("views", "header.html"),
filepath.Join("views", "footer.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("views", "music-gateway.html"),
)),
}
var Components = map[string]*template.Template{
}
var IndexTemplate = template.Must(template.ParseFiles(
filepath.Join("views", "layout.html"),
filepath.Join("views", "header.html"),
filepath.Join("views", "footer.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("views", "index.html"),
))
var MusicTemplate = template.Must(template.ParseFiles(
filepath.Join("views", "layout.html"),
filepath.Join("views", "header.html"),
filepath.Join("views", "footer.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("views", "music.html"),
))
var MusicGatewayTemplate = template.Must(template.ParseFiles(
filepath.Join("views", "layout.html"),
filepath.Join("views", "header.html"),
filepath.Join("views", "footer.html"),
filepath.Join("views", "prideflag.html"),
filepath.Join("views", "music-gateway.html"),
))

View file

@ -47,7 +47,7 @@ func ServeCatalog(app *model.AppState) http.Handler {
}
}
err = templates.Pages["music"].Execute(w, releases)
err = templates.MusicTemplate.Execute(w, releases)
if err != nil {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}
@ -79,7 +79,7 @@ func ServeGateway(app *model.AppState, release *model.Release) http.Handler {
response.Links = release.Links
}
err := templates.Pages["music-gateway"].Execute(w, response)
err := templates.MusicGatewayTemplate.Execute(w, response)
if err != nil {
fmt.Printf("Error rendering music gateway for %s: %s\n", release.ID, err)