summaryrefslogtreecommitdiffstats
path: root/roseh.moe.go
diff options
context:
space:
mode:
Diffstat (limited to 'roseh.moe.go')
-rw-r--r--roseh.moe.go182
1 files changed, 150 insertions, 32 deletions
diff --git a/roseh.moe.go b/roseh.moe.go
index 33122d2..988938d 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -173,7 +173,7 @@ func attachCookie(w http.ResponseWriter) error {
http.SetCookie(w, &http.Cookie{
Name: "auth",
Value: token,
- Path: "/notepad",
+ Path: "/",
Expires: time.Now().Add(cookieExpiration),
Secure: true,
HttpOnly: true,
@@ -205,7 +205,7 @@ func cookieAuth(w http.ResponseWriter, r *http.Request) (string, bool) {
http.SetCookie(w, &http.Cookie{
Name: "csrf-token",
Value: csrfToken,
- Path: "/notepad",
+ Path: "/",
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
@@ -233,6 +233,33 @@ func setEncryptionKey(rawKey cryptoutil.RawKey) error {
return nil
}
+type redirectTarget int
+
+const (
+ redirectDefault redirectTarget = iota
+ redirectNotepad
+ redirectUpload
+)
+
+func parseRedirectTarget(s string) redirectTarget {
+ i, err := strconv.Atoi(s)
+ if err != nil {
+ return redirectDefault
+ }
+ return redirectTarget(i)
+}
+
+func (t redirectTarget) path() string {
+ switch t {
+ case redirectNotepad:
+ return "/notepad"
+ case redirectUpload:
+ return "/notes/upload"
+ default:
+ return "/"
+ }
+}
+
var (
//go:embed templates/login.html.template
loginString string
@@ -240,13 +267,14 @@ var (
)
type loginTemplateArgs struct {
- Error bool
+ Error bool
+ Redirect redirectTarget
}
func login(w http.ResponseWriter, r *http.Request) {
rawKey, err := notepadPassword.CheckPassword(r.FormValue("password"))
if err != nil {
- if err := loginTemplate.Execute(w, loginTemplateArgs{Error: true}); err != nil {
+ if err := loginTemplate.Execute(w, loginTemplateArgs{Error: true, Redirect: parseRedirectTarget(r.FormValue("redirect"))}); err != nil {
log.Printf("Warning: login: %s", err)
}
return
@@ -256,7 +284,7 @@ func login(w http.ResponseWriter, r *http.Request) {
return
}
attachCookie(w)
- http.Redirect(w, r, "/notepad", http.StatusSeeOther)
+ http.Redirect(w, r, parseRedirectTarget(r.FormValue("redirect")).path(), http.StatusSeeOther)
}
func readNotepad(key cryptoutil.EncryptionKey) (string, error) {
@@ -265,7 +293,7 @@ func readNotepad(key cryptoutil.EncryptionKey) (string, error) {
return "", err
}
defer f.Close()
- notepad, err := io.ReadAll(key.NewReader(f, []byte("notepad")))
+ notepad, err := io.ReadAll(key.NewReader(f, cryptoutil.WithAdditionalData([]byte("notepad"))))
if err != nil {
return "", err
}
@@ -286,7 +314,7 @@ type notepadTemplateArgs struct {
func notepad(w http.ResponseWriter, r *http.Request) {
csrfToken, ok := cookieAuth(w, r)
if !ok {
- if err := loginTemplate.Execute(w, loginTemplateArgs{}); err != nil {
+ if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectNotepad}); err != nil {
log.Printf("Warning: login: %s", err)
}
return
@@ -295,7 +323,7 @@ func notepad(w http.ResponseWriter, r *http.Request) {
key := encryptionKey
encryptionKeyMu.Unlock()
if key == nil {
- if err := loginTemplate.Execute(w, loginTemplateArgs{}); err != nil {
+ if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectNotepad}); err != nil {
log.Printf("Warning: login: %s", err)
}
return
@@ -327,7 +355,7 @@ func saveNote(w http.ResponseWriter, r *http.Request) error {
return err
}
defer f.Close()
- encryptingWriter := key.NewWriter(f, []byte("notepad"))
+ encryptingWriter := key.NewWriter(f, cryptoutil.WithAdditionalData([]byte("notepad")))
if _, err := encryptingWriter.Write([]byte(r.FormValue("content"))); err != nil {
os.Remove(f.Name())
return err
@@ -414,7 +442,7 @@ func dumpFile(w io.Writer, key cryptoutil.EncryptionKey, name string) error {
return err
}
defer f.Close()
- _, err = io.Copy(w, key.NewReader(f, []byte("chunks/"+filepath.Base(name))))
+ _, err = io.Copy(w, key.NewReader(f, cryptoutil.WithAdditionalData([]byte("chunks/"+filepath.Base(name)))))
return err
}
@@ -428,6 +456,27 @@ var (
wordList = strings.Split(strings.TrimSuffix(wordListString, "\n"), "\n")
)
+func newNoteName() (*os.File, error) {
+ for n := 1; ; n++ {
+ buf := make([]byte, 2*n)
+ rand.Read(buf)
+ words := make([]string, n)
+ for i := range words {
+ words[i] = wordList[binary.NativeEndian.Uint16(buf[2*i:])&0x1fff]
+ }
+ name := strings.Join(words, "-")
+ var err error
+ f, err := os.OpenFile(*notepadDir+"/notes/"+name, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
+ if err != nil {
+ if errors.Is(err, os.ErrExist) {
+ continue
+ }
+ return nil, fmt.Errorf("create note: create note file: %s", err)
+ }
+ return f, nil
+ }
+}
+
func createNote(req *api.CreateNoteRequest) (*api.CreateNoteResponse, error) {
type continuationToken struct {
Prefix string
@@ -479,7 +528,7 @@ func createNote(req *api.CreateNoteRequest) (*api.CreateNoteResponse, error) {
return nil, err
}
}
- encryptingWriter := key.NewWriter(f, []byte("chunks/"+name))
+ encryptingWriter := key.NewWriter(f, cryptoutil.WithAdditionalData([]byte("chunks/"+name)))
if _, err := encryptingWriter.Write(req.Chunk); err != nil {
return nil, err
}
@@ -499,27 +548,12 @@ func createNote(req *api.CreateNoteRequest) (*api.CreateNoteResponse, error) {
if err := os.MkdirAll(*notepadDir+"/notes", 0700); err != nil {
return nil, err
}
- var finalNote *os.File
- for n := 1; ; n++ {
- buf := make([]byte, 2*n)
- rand.Read(buf)
- words := make([]string, n)
- for i := range words {
- words[i] = wordList[binary.NativeEndian.Uint16(buf[2*i:])&0x1fff]
- }
- name := strings.Join(words, "-")
- var err error
- finalNote, err = os.OpenFile(*notepadDir+"/notes/"+name, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0600)
- if err != nil {
- if errors.Is(err, os.ErrExist) {
- continue
- }
- return nil, fmt.Errorf("create note: create note file: %s", err)
- }
- defer finalNote.Close()
- break
+ finalNote, err := newNoteName()
+ if err != nil {
+ return nil, err
}
- writer := key.NewWriter(finalNote, []byte("notes/"+filepath.Base(finalNote.Name())))
+ defer finalNote.Close()
+ writer := key.NewWriter(finalNote, cryptoutil.WithAdditionalData([]byte("notes/"+filepath.Base(finalNote.Name()))))
for i := range contToken.I {
if err := dumpFile(writer, key, *notepadDir+"/chunks/"+chunkName(contToken.Prefix, i)); err != nil {
return nil, err
@@ -568,7 +602,7 @@ func readNote(stream *encoder, req *api.ReadNoteRequest) error {
if key == nil {
return fmt.Errorf("%w: need login", api.PermissionDenied)
}
- _, err = io.Copy(&readNoteResponseWriter{stream: stream}, key.NewReader(f, []byte("notes/"+req.Note)))
+ _, err = io.Copy(&readNoteResponseWriter{stream: stream}, key.NewReader(f, cryptoutil.WithAdditionalData([]byte("notes/"+req.Note))))
return err
}
@@ -624,6 +658,87 @@ func gobRespStreamMiddleware[Request any](next func(*encoder, *Request) error) h
}
}
+var (
+ //go:embed templates/upload.html.template
+ uploadTemplateString string
+ uploadTemplate = template.Must(template.Must(outlineTemplate.Clone()).New("body").Parse(uploadTemplateString)).Lookup("outline")
+)
+
+type uploadTemplateArgs struct {
+ CSRFToken string
+}
+
+func uploadPage(w http.ResponseWriter, r *http.Request) {
+ csrfToken, ok := cookieAuth(w, r)
+ if !ok {
+ if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectUpload}); err != nil {
+ log.Printf("Warning: login: %s", err)
+ }
+ return
+ }
+ encryptionKeyMu.Lock()
+ key := encryptionKey
+ encryptionKeyMu.Unlock()
+ if key == nil {
+ if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectUpload}); err != nil {
+ log.Printf("Warning: login: %s", err)
+ }
+ return
+ }
+ if err := uploadTemplate.Execute(w, uploadTemplateArgs{CSRFToken: csrfToken}); err != nil {
+ log.Printf("Warning: upload: %s", err)
+ }
+}
+
+func uploadNote(w http.ResponseWriter, r *http.Request) {
+ if csrfToken, err := r.Cookie("csrf-token"); err != nil || subtle.ConstantTimeCompare([]byte(csrfToken.Value), []byte(r.FormValue("csrf-token"))) == 0 {
+ http.Error(w, "bad CSRF token", http.StatusForbidden)
+ return
+ }
+ if _, ok := cookieAuth(w, r); !ok {
+ log.Printf("asdf: bad auth")
+ if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectUpload}); err != nil {
+ log.Printf("Warning: login: %s", err)
+ }
+ return
+ }
+ encryptionKeyMu.Lock()
+ key := encryptionKey
+ encryptionKeyMu.Unlock()
+ if key == nil {
+ log.Printf("asdf: key nil")
+ if err := loginTemplate.Execute(w, loginTemplateArgs{Redirect: redirectUpload}); err != nil {
+ log.Printf("Warning: login: %s", err)
+ }
+ return
+ }
+ noteFile, err := newNoteName()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ defer noteFile.Close()
+ contentReader, _, err := r.FormFile("content")
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusBadRequest)
+ return
+ }
+ writer := key.NewWriter(noteFile, cryptoutil.WithAdditionalData([]byte("notes/"+filepath.Base(noteFile.Name()))))
+ if _, err := io.Copy(writer, contentReader); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err := writer.Close(); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err := noteFile.Close(); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ fmt.Fprintf(w, "upload! pog\n%s", filepath.Base(noteFile.Name()))
+}
+
func main() {
flag.Parse()
@@ -636,6 +751,9 @@ func main() {
http.HandleFunc("POST /login", login)
http.HandleFunc("GET /notepad", notepad)
http.HandleFunc("POST /notepad/autosave", autosave)
+ http.HandleFunc("GET /notes/upload", uploadPage)
+ http.HandleFunc("POST /notes/upload", uploadNote)
+ // http.HandleFunc("GET /notes/{note}", readNoteContent)
http.HandleFunc("POST /api/login", apiLogin)
http.HandleFunc("GET /api/list-notes", gobReqRespMiddleware(listNotes))
http.HandleFunc("POST /api/create-note", gobReqRespMiddleware(createNote))