diff options
Diffstat (limited to 'roseh.moe.go')
| -rw-r--r-- | roseh.moe.go | 182 |
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)) |
