91 lines
2.8 KiB
Go
91 lines
2.8 KiB
Go
package webui
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"net/http"
|
|
)
|
|
|
|
// Flash mirrors one Flask flash() message: (message, category).
|
|
type Flash struct {
|
|
Category string `json:"c"`
|
|
Message string `json:"m"`
|
|
}
|
|
|
|
const flashCookieName = "flash"
|
|
|
|
// setFlash mirrors flask.flash(): appends one message to the flash cookie so it
|
|
// survives the redirect that (almost) always follows a form POST in this app.
|
|
// No signing: these are cosmetic toast notifications, not a trust boundary.
|
|
func setFlash(w http.ResponseWriter, category, message string) {
|
|
flashes := []Flash{{Category: category, Message: message}}
|
|
encoded, _ := json.Marshal(flashes)
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: flashCookieName,
|
|
Value: base64.URLEncoding.EncodeToString(encoded),
|
|
Path: "/",
|
|
HttpOnly: true,
|
|
SameSite: http.SameSiteLaxMode,
|
|
})
|
|
}
|
|
|
|
// popFlashes mirrors get_flashed_messages(with_categories=true): reads and clears the
|
|
// flash cookie so each message is shown exactly once, on the very next render.
|
|
func popFlashes(w http.ResponseWriter, r *http.Request) []Flash {
|
|
c, err := r.Cookie(flashCookieName)
|
|
if err != nil || c.Value == "" {
|
|
return nil
|
|
}
|
|
http.SetCookie(w, &http.Cookie{Name: flashCookieName, Value: "", Path: "/", MaxAge: -1})
|
|
raw, err := base64.URLEncoding.DecodeString(c.Value)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var flashes []Flash
|
|
if err := json.Unmarshal(raw, &flashes); err != nil {
|
|
return nil
|
|
}
|
|
return flashes
|
|
}
|
|
|
|
// AppPasswordReveal carries a freshly-generated app password secret across the
|
|
// create->redirect hop, kept out of the toast-driven Flash system so it can render
|
|
// as its own centered modal (with a copy button) instead of a toast that's easy to
|
|
// miss and can't be copied without retyping.
|
|
type AppPasswordReveal struct {
|
|
Label string `json:"l"`
|
|
Secret string `json:"s"`
|
|
}
|
|
|
|
const appPasswordRevealCookieName = "app_pw_reveal"
|
|
|
|
// setAppPasswordReveal mirrors setFlash but for the one-time secret reveal.
|
|
func setAppPasswordReveal(w http.ResponseWriter, label, secret string) {
|
|
encoded, _ := json.Marshal(AppPasswordReveal{Label: label, Secret: secret})
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: appPasswordRevealCookieName,
|
|
Value: base64.URLEncoding.EncodeToString(encoded),
|
|
Path: "/",
|
|
HttpOnly: true,
|
|
SameSite: http.SameSiteLaxMode,
|
|
})
|
|
}
|
|
|
|
// popAppPasswordReveal mirrors popFlashes but for the one-time secret reveal.
|
|
func popAppPasswordReveal(w http.ResponseWriter, r *http.Request) *AppPasswordReveal {
|
|
c, err := r.Cookie(appPasswordRevealCookieName)
|
|
if err != nil || c.Value == "" {
|
|
return nil
|
|
}
|
|
http.SetCookie(w, &http.Cookie{Name: appPasswordRevealCookieName, Value: "", Path: "/", MaxAge: -1})
|
|
raw, err := base64.URLEncoding.DecodeString(c.Value)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var reveal AppPasswordReveal
|
|
if err := json.Unmarshal(raw, &reveal); err != nil {
|
|
return nil
|
|
}
|
|
return &reveal
|
|
}
|