summaryrefslogtreecommitdiffstats
path: root/roseh.moe.go
diff options
context:
space:
mode:
Diffstat (limited to 'roseh.moe.go')
-rw-r--r--roseh.moe.go138
1 files changed, 100 insertions, 38 deletions
diff --git a/roseh.moe.go b/roseh.moe.go
index f9c5478..76b4dc1 100644
--- a/roseh.moe.go
+++ b/roseh.moe.go
@@ -133,12 +133,16 @@ func favicon(w http.ResponseWriter, r *http.Request) {
serveStaticFile(w, r, "static/favicon.ico")
}
+type authToken struct {
+ Issued time.Time
+}
+
func makeToken() (string, error) {
- nowBytes, err := time.Now().MarshalBinary()
- if err != nil {
+ buf := new(bytes.Buffer)
+ if err := gob.NewEncoder(buf).Encode(authToken{Issued: time.Now()}); err != nil {
return "", err
}
- return base64.RawStdEncoding.EncodeToString(secretKey.Sign(nowBytes)), nil
+ return base64.RawStdEncoding.EncodeToString(secretKey.Sign(buf.Bytes(), "auth")), nil
}
func checkToken(token string) bool {
@@ -146,15 +150,15 @@ func checkToken(token string) bool {
if err != nil {
return false
}
- msg, ok := secretKey.Verify(authCookie)
+ msg, ok := secretKey.Verify(authCookie, "auth")
if !ok {
return false
}
- var t time.Time
- if err := t.UnmarshalBinary(msg); err != nil {
+ var t authToken
+ if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(&t); err != nil {
return false
}
- return time.Since(t) < cookieExpiration
+ return time.Since(t.Issued) < cookieExpiration
}
const cookieExpiration = 180 * 24 * time.Hour
@@ -402,13 +406,84 @@ func listNotes(req *api.ListNotesRequest) (*api.ListNotesResponse, error) {
return &api.ListNotesResponse{Notes: names}, nil
}
+type countingWriter struct {
+ w io.Writer
+ count int64
+}
+
+func (w *countingWriter) Write(buf []byte) (int, error) {
+ n, err := w.w.Write(buf)
+ w.count += int64(n)
+ return n, err
+}
+
+type continuationToken struct {
+ Name string
+ Size int64
+}
+
+func appendNote(req *api.CreateNoteRequest, key cryptoutil.EncryptionKey) (*api.CreateNoteResponse, error) {
+ msg, ok := secretKey.Verify(req.ContinuationToken, "continuation-token")
+ if !ok {
+ return nil, fmt.Errorf("%w: bad continuation token", api.InvalidArgument)
+ }
+ var token continuationToken
+ if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(&token); err != nil {
+ return nil, fmt.Errorf("failed to decode continuation token: %s", err)
+ }
+ var err error
+ oldNote, err := os.Open(*notepadDir + "/notes/" + token.Name)
+ if err != nil {
+ return nil, err
+ }
+ defer oldNote.Close()
+ stat, err := oldNote.Stat()
+ if err != nil {
+ return nil, err
+ }
+ if token.Size != stat.Size() {
+ return nil, fmt.Errorf("%w: continuation token expired", api.InvalidArgument)
+ }
+ newNote, err := os.CreateTemp(*notepadDir+"/notes", token.Name)
+ if err != nil {
+ return nil, err
+ }
+ defer newNote.Close()
+ additionalData := []byte("notes/" + token.Name)
+ cw := &countingWriter{w: newNote}
+ encryptingWriter := key.NewWriter(cw, additionalData)
+ if _, err := io.Copy(encryptingWriter, key.NewReader(oldNote, additionalData)); err != nil {
+ return nil, err
+ }
+ if _, err := encryptingWriter.Write(req.Chunk); err != nil {
+ return nil, err
+ }
+ if err := encryptingWriter.Close(); err != nil {
+ return nil, err
+ }
+ if err := newNote.Close(); err != nil {
+ return nil, err
+ }
+ if err := os.Rename(newNote.Name(), *notepadDir+"/notes/"+token.Name); err != nil {
+ return nil, err
+ }
+ buf := new(bytes.Buffer)
+ if err := gob.NewEncoder(buf).Encode(continuationToken{Name: token.Name, Size: cw.count}); err != nil {
+ return nil, err
+ }
+ return &api.CreateNoteResponse{
+ Name: token.Name,
+ ContinuationToken: secretKey.Sign(buf.Bytes(), "continuation-token"),
+ }, nil
+}
+
var (
//go:embed wordlist.txt
wordListString string
wordList = strings.Split(strings.TrimSuffix(wordListString, "\n"), "\n")
)
-func createNote(stream *gob.Decoder) (*api.CreateNoteResponse, error) {
+func createNote(req *api.CreateNoteRequest) (*api.CreateNoteResponse, error) {
encryptionKeyMu.Lock()
key := encryptionKey
encryptionKeyMu.Unlock()
@@ -417,6 +492,9 @@ func createNote(stream *gob.Decoder) (*api.CreateNoteResponse, error) {
}
var name string
var f *os.File
+ if len(req.ContinuationToken) > 0 {
+ return appendNote(req, key)
+ }
for n := 1; ; n++ {
buf := make([]byte, 2*n)
rand.Read(buf)
@@ -433,21 +511,13 @@ func createNote(stream *gob.Decoder) (*api.CreateNoteResponse, error) {
}
return nil, fmt.Errorf("create note: create note file: %s", err)
}
+ defer f.Close()
break
}
- defer f.Close()
- encryptingWriter := key.NewWriter(f, []byte("notes/"+name))
- for {
- req := new(api.CreateNoteRequestStream)
- if err := stream.Decode(req); err != nil {
- if errors.Is(err, io.EOF) {
- break
- }
- return nil, fmt.Errorf("create note: read input stream: %s", err)
- }
- if _, err := encryptingWriter.Write(req.Chunk); err != nil {
- return nil, fmt.Errorf("create note: write note to file: %s", err)
- }
+ cw := &countingWriter{w: f}
+ encryptingWriter := key.NewWriter(cw, []byte("notes/"+name))
+ if _, err := encryptingWriter.Write(req.Chunk); err != nil {
+ return nil, err
}
if err := encryptingWriter.Close(); err != nil {
return nil, err
@@ -455,7 +525,14 @@ func createNote(stream *gob.Decoder) (*api.CreateNoteResponse, error) {
if err := f.Close(); err != nil {
return nil, fmt.Errorf("create note: write file: %s", err)
}
- return &api.CreateNoteResponse{Name: name}, nil
+ token := new(bytes.Buffer)
+ if err := gob.NewEncoder(token).Encode(continuationToken{Name: name, Size: cw.count}); err != nil {
+ return nil, err
+ }
+ return &api.CreateNoteResponse{
+ Name: name,
+ ContinuationToken: secretKey.Sign(token.Bytes(), "continuation-token"),
+ }, nil
}
type readNoteResponseWriter struct {
@@ -521,21 +598,6 @@ func gobReqRespMiddleware[Request, Response any](next func(*Request) (*Response,
}
}
-func gobReqStreamMiddleware[Response any](next func(*gob.Decoder) (*Response, error)) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "application/gob")
- if !tokenAuth(w, r) {
- return
- }
- resp, err := next(gob.NewDecoder(r.Body))
- if err != nil {
- gobError(w, err)
- return
- }
- writeGob(w, resp)
- }
-}
-
func gobRespStreamMiddleware[Request any](next func(*encoder, *Request) error) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/gob")
@@ -569,7 +631,7 @@ func main() {
http.HandleFunc("POST /notepad/autosave", autosave)
http.HandleFunc("POST /api/login", apiLogin)
http.HandleFunc("GET /api/list-notes", gobReqRespMiddleware(listNotes))
- http.HandleFunc("POST /api/create-note", gobReqStreamMiddleware(createNote))
+ http.HandleFunc("POST /api/create-note", gobReqRespMiddleware(createNote))
http.HandleFunc("GET /api/read-note", gobRespStreamMiddleware(readNote))
http.HandleFunc("GET /static/", static)
http.HandleFunc("GET /favicon.ico", favicon)