summaryrefslogtreecommitdiffstats
path: root/roseh.moe.go
diff options
context:
space:
mode:
authorRose Hogenson <rosehogenson@posteo.net>2025-09-20 17:14:11 -0700
committerRose Hogenson <rosehogenson@posteo.net>2025-09-20 17:14:11 -0700
commitc2ed468674e7db0a2d2ad339da76a89b12db3e8c (patch)
tree9a612980950c7fd382c94c9921f974800203ea5b /roseh.moe.go
parent7a0305243d73edcd6d4c3870a4d3a8b232637d93 (diff)
downloadroseh.moe-c2ed468674e7db0a2d2ad339da76a89b12db3e8c.tar.zst
Add manual save
Diffstat (limited to 'roseh.moe.go')
-rw-r--r--roseh.moe.go108
1 files changed, 72 insertions, 36 deletions
diff --git a/roseh.moe.go b/roseh.moe.go
index 5e9dd92..da88003 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -30,7 +30,8 @@ var (
var (
notepadPassword []byte
- secretKey ed25519.PrivateKey
+ privateKey ed25519.PrivateKey
+ publicKey ed25519.PublicKey
)
func loadSecrets() error {
@@ -38,7 +39,7 @@ func loadSecrets() error {
if err != nil {
return err
}
- for _, line := range bytes.Split(bytes.TrimSpace(secrets), []byte("\n")) {
+ for _, line := range bytes.Split(bytes.TrimSuffix(secrets, []byte("\n")), []byte("\n")) {
if pw, ok := bytes.CutPrefix(line, []byte("notepad-password=")); ok {
notepadPassword = make([]byte, hex.DecodedLen(len(pw)))
if _, err := hex.Decode(notepadPassword, pw); err != nil {
@@ -52,7 +53,8 @@ func loadSecrets() error {
if len(seed) != ed25519.SeedSize {
return fmt.Errorf("invalid ed25519 key")
}
- secretKey = ed25519.NewKeyFromSeed(seed)
+ privateKey = ed25519.NewKeyFromSeed(seed)
+ publicKey = privateKey.Public().(ed25519.PublicKey)
}
}
return nil
@@ -81,11 +83,11 @@ var (
//go:embed templates/index.html.template
indexString string
- indexTemplate = template.Must(template.Must(outlineTemplate.Clone()).New("body").Parse(indexString))
+ indexTemplate = template.Must(template.Must(outlineTemplate.Clone()).New("body").Parse(indexString)).Lookup("outline")
)
func index(w http.ResponseWriter, _ *http.Request) {
- if err := indexTemplate.ExecuteTemplate(w, "outline", nil); err != nil {
+ if err := indexTemplate.Execute(w, nil); err != nil {
log.Printf("Warning: index: %s", err)
}
}
@@ -130,7 +132,7 @@ func attachCookie(w http.ResponseWriter) error {
if err != nil {
return err
}
- sig := ed25519.Sign(secretKey, nowBytes)
+ sig := ed25519.Sign(privateKey, nowBytes)
http.SetCookie(w, &http.Cookie{
Name: "auth",
Value: base64.RawStdEncoding.EncodeToString(append(nowBytes, sig...)),
@@ -144,8 +146,12 @@ func attachCookie(w http.ResponseWriter) error {
return nil
}
-func cookieOK(cookie string) bool {
- bytes, err := base64.RawStdEncoding.DecodeString(cookie)
+func checkCookie(w http.ResponseWriter, r *http.Request) bool {
+ cookie, err := r.Cookie("auth")
+ if err != nil {
+ return false
+ }
+ bytes, err := base64.RawStdEncoding.DecodeString(cookie.Value)
if err != nil {
return false
}
@@ -153,38 +159,46 @@ func cookieOK(cookie string) bool {
return false
}
msg, sig := bytes[:len(bytes)-ed25519.SignatureSize], bytes[len(bytes)-ed25519.SignatureSize:]
- if !ed25519.Verify(secretKey.Public().(ed25519.PublicKey), msg, sig) {
+ if !ed25519.Verify(publicKey, msg, sig) {
return false
}
var t time.Time
if err := t.UnmarshalBinary(msg); err != nil {
return false
}
- return time.Since(t) < cookieExpiration
+ if time.Since(t) > cookieExpiration {
+ return false
+ }
+ attachCookie(w)
+ return true
}
-func authenticate(next http.Handler) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- if cookie, err := r.Cookie("auth"); err == nil && cookieOK(cookie.Value) {
- attachCookie(w)
- next.ServeHTTP(w, r)
- return
- }
- _, password, ok := r.BasicAuth()
- if !ok {
- w.Header().Set("WWW-Authenticate", `Basic realm="notepad"`)
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
- pwHash := sha512.Sum512([]byte(password))
- if subtle.ConstantTimeCompare(pwHash[:], notepadPassword) == 0 {
- w.Header().Set("WWW-Authenticate", `Basic realm="notepad"`)
- http.Error(w, "Unauthorized", http.StatusUnauthorized)
- return
- }
- attachCookie(w)
- next.ServeHTTP(w, r)
+func cookieAuth(w http.ResponseWriter, r *http.Request) bool {
+ if !checkCookie(w, r) {
+ http.Error(w, "Forbidden", http.StatusForbidden)
+ return false
+ }
+ return true
+}
+
+func basicAuth(w http.ResponseWriter, r *http.Request) bool {
+ if checkCookie(w, r) {
+ return true
+ }
+ _, password, ok := r.BasicAuth()
+ if !ok {
+ w.Header().Set("WWW-Authenticate", `Basic realm="notepad"`)
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return false
+ }
+ pwHash := sha512.Sum512([]byte(password))
+ if subtle.ConstantTimeCompare(pwHash[:], notepadPassword) == 0 {
+ w.Header().Set("WWW-Authenticate", `Basic realm="notepad"`)
+ http.Error(w, "Unauthorized", http.StatusUnauthorized)
+ return false
}
+ attachCookie(w)
+ return true
}
var (
@@ -193,17 +207,21 @@ var (
notepadTemplate = template.Must(template.Must(outlineTemplate.Clone()).New("body").Parse(notepadString)).Lookup("outline")
)
+type notepadTemplateArgs struct {
+ Content string
+}
+
func notepad(w http.ResponseWriter, r *http.Request) {
+ if !basicAuth(w, r) {
+ return
+ }
var currentContent string
if currentContentBytes, err := os.ReadFile(*notepadFile); err != nil {
currentContent = fmt.Sprintf("Error reading notepad file: %s", err)
} else {
currentContent = string(currentContentBytes)
}
- type args struct {
- Content string
- }
- if err := notepadTemplate.Execute(w, args{Content: currentContent}); err != nil {
+ if err := notepadTemplate.Execute(w, notepadTemplateArgs{Content: currentContent}); err != nil {
log.Printf("Warning: notepad: %s", err)
}
}
@@ -224,6 +242,9 @@ func saveNote(content []byte) error {
}
func autosave(w http.ResponseWriter, r *http.Request) {
+ if !cookieAuth(w, r) {
+ return
+ }
msg := "Saved ✓"
if err := saveNote([]byte(r.FormValue("content"))); err != nil {
msg = fmt.Sprintf("Error: %s", err)
@@ -233,6 +254,20 @@ func autosave(w http.ResponseWriter, r *http.Request) {
}
}
+func manualSave(w http.ResponseWriter, r *http.Request) {
+ if !cookieAuth(w, r) {
+ return
+ }
+ content := r.FormValue("content")
+ if err := saveNote([]byte(content)); err != nil {
+ http.Error(w, fmt.Sprintf("Error: %s", err), http.StatusInternalServerError)
+ return
+ }
+ if err := notepadTemplate.Execute(w, notepadTemplateArgs{Content: content}); err != nil {
+ log.Printf("Warning: manualSave: %s", err)
+ }
+}
+
func main() {
flag.Parse()
@@ -242,7 +277,8 @@ func main() {
}
http.HandleFunc("GET /pong", pong)
- http.Handle("GET /notepad", authenticate(http.HandlerFunc(notepad)))
+ http.HandleFunc("GET /notepad", notepad)
+ http.HandleFunc("POST /notepad", manualSave)
http.HandleFunc("POST /notepad/autosave", autosave)
http.HandleFunc("GET /static/", static)
http.HandleFunc("GET /favicon.ico", favicon)