From de241469911c374fd73e8c745cdeee4345821e44 Mon Sep 17 00:00:00 2001 From: Julien Riou Date: Thu, 2 Oct 2025 07:06:54 +0200 Subject: [PATCH] feat: Encode password Fixes #38. Signed-off-by: Julien Riou --- src/cmd/coller/main.go | 11 +++++++---- src/cmd/collerd/README.md | 4 ++-- src/cmd/copier/main.go | 18 ++++++++++++++---- src/server/db.go | 6 +++--- src/server/errors.go | 4 +++- src/server/handlers_api.go | 17 ++++++++++++----- src/server/handlers_web.go | 2 +- 7 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/cmd/coller/main.go b/src/cmd/coller/main.go index ce138c9..bb820d2 100644 --- a/src/cmd/coller/main.go +++ b/src/cmd/coller/main.go @@ -38,7 +38,6 @@ type NotePayload struct { type NoteResponse struct { ID int64 `json:"id"` Message string `json:"message,omitempty"` - Error string `json:"error,omitempty"` } type Config struct { @@ -192,8 +191,12 @@ func handleMain() int { } logger.Debug("encoding content") - encoded := internal.Encode(content) - p.Content = encoded + encodedContent := internal.Encode(content) + p.Content = encodedContent + + logger.Debug("encoding password") + encodedPassword := internal.Encode([]byte(*password)) + p.Password = encodedPassword payload, err := json.Marshal(p) if err != nil { @@ -241,7 +244,7 @@ func handleMain() int { } if r.StatusCode != http.StatusOK { - return internal.ReturnError(logger, jsonBody.Message, fmt.Errorf("%s", jsonBody.Error)) + return internal.ReturnError(logger, jsonBody.Message, nil) } logger.Debug("finding note location") diff --git a/src/cmd/collerd/README.md b/src/cmd/collerd/README.md index a6fe160..9c0aae1 100644 --- a/src/cmd/collerd/README.md +++ b/src/cmd/collerd/README.md @@ -82,7 +82,7 @@ Return content of a note encrypted by the given encryption key. Return content of a protected note encrypted by the given encryption key. Body (JSON): -* **password** (string): password used to protect the note (required) +* **password** (string): base64 encoded password used to protect the note (required) ### GET /api/note/\ @@ -97,7 +97,7 @@ Return content of a protected note. If the note is encrypted, the encrypted value is returned (application/octet-stream). Otherwise, the text is returned (text/plain). Body (JSON): -* **password** (string): password used to protect the note (required) +* **password** (string): base64 encoded password used to protect the note (required) ### Errors diff --git a/src/cmd/copier/main.go b/src/cmd/copier/main.go index 0f9c062..17dd9e5 100644 --- a/src/cmd/copier/main.go +++ b/src/cmd/copier/main.go @@ -28,6 +28,11 @@ type NotePayload struct { Password string `json:"password"` } +type NoteResponse struct { + ID int64 `json:"id"` + Message string `json:"message,omitempty"` +} + func handleMain() int { flag.Usage = usage @@ -134,16 +139,21 @@ func handleMain() int { return internal.ReturnError(logger, "could not retreive note", err) } - if r.StatusCode >= 300 { - return internal.ReturnError(logger, "could not retreive note", fmt.Errorf("status code %d", r.StatusCode)) - } - logger.Debug("decoding body") body, err := io.ReadAll(r.Body) if err != nil { return internal.ReturnError(logger, "could not read response", err) } + if r.StatusCode != http.StatusOK { + jsonBody := &NoteResponse{} + err = json.Unmarshal(body, jsonBody) + if err != nil { + return internal.ReturnError(logger, "could not decode response", err) + } + return internal.ReturnError(logger, jsonBody.Message, nil) + } + var content []byte if *encryptionKey != "" { logger.Debug("decrypting note") diff --git a/src/server/db.go b/src/server/db.go index f8c5e38..d8b81f0 100644 --- a/src/server/db.go +++ b/src/server/db.go @@ -123,7 +123,7 @@ func (d *Database) Get(id string) (*Note, error) { return nil, nil } -func (d *Database) Create(content []byte, password string, encryptionKey string, encrypted bool, expiration int, deleteAfterRead bool, language string) (note *Note, err error) { +func (d *Database) Create(content []byte, password []byte, encryptionKey string, encrypted bool, expiration int, deleteAfterRead bool, language string) (note *Note, err error) { if expiration == 0 { expiration = d.expiration } @@ -161,8 +161,8 @@ func (d *Database) Create(content []byte, password string, encryptionKey string, note.Encrypted = true } - if password != "" { - hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if password != nil { + hash, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost) if err != nil { return nil, err } diff --git a/src/server/errors.go b/src/server/errors.go index a863d63..f685728 100644 --- a/src/server/errors.go +++ b/src/server/errors.go @@ -9,6 +9,8 @@ var ( ErrEncryptionKeyNotFound = errors.New("encryption key not found") ErrCouldNotDecryptNote = errors.New("could not decrypt note") ErrInvalidPassword = errors.New("invalid password") + ErrInvalidExpiration = errors.New("invalid expiration") + ErrInvalidLanguage = errors.New("invalid language") ErrCouldNotParseFile = errors.New("could not parse file") ErrFileTooLarge = errors.New("file too large") ErrTextFileExpected = errors.New("text file expected") @@ -16,9 +18,9 @@ var ( ErrEmptyNote = errors.New("empty note") ErrEncryptionRequired = errors.New("encryption is required") ErrClientEncryptionKeyNotAllowed = errors.New("client encryption key is not allowed") - ErrInvalidExpiration = errors.New("invalid expiration") ErrCouldNotCreateNote = errors.New("could not create note") ErrCouldNotDecodePayload = errors.New("could not decode payload") ErrCouldNotDecodeContent = errors.New("could not decode content") + ErrCouldNotDecodePassword = errors.New("could not decode password") ErrNoteIsPasswordProtected = errors.New("note is password protected") ) diff --git a/src/server/handlers_api.go b/src/server/handlers_api.go index 0c2dccf..d41a254 100644 --- a/src/server/handlers_api.go +++ b/src/server/handlers_api.go @@ -28,6 +28,8 @@ func apiError(level int, w http.ResponseWriter, logger *slog.Logger, topLevelErr // Wrap error for logging if err != nil { err = fmt.Errorf("%v: %w", topLevelErr, err) + } else { + err = topLevelErr } logger.Error(fmt.Sprintf("%v", err)) @@ -106,13 +108,18 @@ func (h *CreateNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } content, err := internal.Decode(body.Content) - if err != nil { APIError(w, logger, ErrCouldNotDecodeContent, err) return } - note, err := h.db.Create(content, body.Password, body.EncryptionKey, body.Encrypted, body.Expiration, body.DeleteAfterRead, body.Language) + password, err := internal.Decode(body.Password) + if err != nil { + APIError(w, logger, ErrCouldNotDecodePassword, err) + return + } + + note, err := h.db.Create(content, password, body.EncryptionKey, body.Encrypted, body.Expiration, body.DeleteAfterRead, body.Language) if err != nil { APIError(w, logger, ErrCouldNotCreateNote, err) return @@ -143,9 +150,9 @@ func (h *GetNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if err != nil { APIError(w, logger, ErrCouldNotFindNote, err) } else if note == nil { - APIErrorNotFound(w, logger, ErrNoteDoesNotExist, err) + APIErrorNotFound(w, logger, ErrNoteDoesNotExist, nil) } else if note.PasswordHash != nil { - APIErrorBadRequest(w, logger, ErrNoteIsPasswordProtected, err) + APIErrorBadRequest(w, logger, ErrNoteIsPasswordProtected, nil) } else { if note.Encrypted { w.Header().Set("Content-Type", "application/octet-stream") @@ -209,7 +216,7 @@ func (h *GetProtectedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque if len(note.PasswordHash) > 0 { err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(body.Password)) if err != nil { - APIError(w, logger, ErrInvalidPassword, err) + APIErrorBadRequest(w, logger, ErrInvalidPassword, err) return } } diff --git a/src/server/handlers_web.go b/src/server/handlers_web.go index 5198194..fc24035 100644 --- a/src/server/handlers_web.go +++ b/src/server/handlers_web.go @@ -170,7 +170,7 @@ func (h *CreateNoteWithFormHandler) ServeHTTP(w http.ResponseWriter, r *http.Req } logger.Debug("saving note to the database") - note, err := h.db.Create(content, password, encryptionKey, encryptionKey != "", expirationInt, deleteAfterRead != "", language) + note, err := h.db.Create(content, []byte(password), encryptionKey, encryptionKey != "", expirationInt, deleteAfterRead != "", language) if err != nil { h.WebError(w, logger, ErrCouldNotCreateNote, err) return