feat: Encode password
All checks were successful
/ pre-commit (push) Successful in 1m14s

Fixes #38.

Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
Julien Riou 2025-10-02 07:06:54 +02:00
commit de24146991
Signed by: jriou
GPG key ID: 9A099EDA51316854
7 changed files with 42 additions and 20 deletions

View file

@ -38,7 +38,6 @@ type NotePayload struct {
type NoteResponse struct { type NoteResponse struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Message string `json:"message,omitempty"` Message string `json:"message,omitempty"`
Error string `json:"error,omitempty"`
} }
type Config struct { type Config struct {
@ -192,8 +191,12 @@ func handleMain() int {
} }
logger.Debug("encoding content") logger.Debug("encoding content")
encoded := internal.Encode(content) encodedContent := internal.Encode(content)
p.Content = encoded p.Content = encodedContent
logger.Debug("encoding password")
encodedPassword := internal.Encode([]byte(*password))
p.Password = encodedPassword
payload, err := json.Marshal(p) payload, err := json.Marshal(p)
if err != nil { if err != nil {
@ -241,7 +244,7 @@ func handleMain() int {
} }
if r.StatusCode != http.StatusOK { 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") logger.Debug("finding note location")

View file

@ -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. Return content of a protected note encrypted by the given encryption key.
Body (JSON): 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/\<id\> ### GET /api/note/\<id\>
@ -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). If the note is encrypted, the encrypted value is returned (application/octet-stream). Otherwise, the text is returned (text/plain).
Body (JSON): Body (JSON):
* **password** (string): password used to protect the note (required) * **password** (string): base64 encoded password used to protect the note (required)
### Errors ### Errors

View file

@ -28,6 +28,11 @@ type NotePayload struct {
Password string `json:"password"` Password string `json:"password"`
} }
type NoteResponse struct {
ID int64 `json:"id"`
Message string `json:"message,omitempty"`
}
func handleMain() int { func handleMain() int {
flag.Usage = usage flag.Usage = usage
@ -134,16 +139,21 @@ func handleMain() int {
return internal.ReturnError(logger, "could not retreive note", err) 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") logger.Debug("decoding body")
body, err := io.ReadAll(r.Body) body, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
return internal.ReturnError(logger, "could not read response", err) 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 var content []byte
if *encryptionKey != "" { if *encryptionKey != "" {
logger.Debug("decrypting note") logger.Debug("decrypting note")

View file

@ -123,7 +123,7 @@ func (d *Database) Get(id string) (*Note, error) {
return nil, nil 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 { if expiration == 0 {
expiration = d.expiration expiration = d.expiration
} }
@ -161,8 +161,8 @@ func (d *Database) Create(content []byte, password string, encryptionKey string,
note.Encrypted = true note.Encrypted = true
} }
if password != "" { if password != nil {
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) hash, err := bcrypt.GenerateFromPassword(password, bcrypt.DefaultCost)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -9,6 +9,8 @@ var (
ErrEncryptionKeyNotFound = errors.New("encryption key not found") ErrEncryptionKeyNotFound = errors.New("encryption key not found")
ErrCouldNotDecryptNote = errors.New("could not decrypt note") ErrCouldNotDecryptNote = errors.New("could not decrypt note")
ErrInvalidPassword = errors.New("invalid password") ErrInvalidPassword = errors.New("invalid password")
ErrInvalidExpiration = errors.New("invalid expiration")
ErrInvalidLanguage = errors.New("invalid language")
ErrCouldNotParseFile = errors.New("could not parse file") ErrCouldNotParseFile = errors.New("could not parse file")
ErrFileTooLarge = errors.New("file too large") ErrFileTooLarge = errors.New("file too large")
ErrTextFileExpected = errors.New("text file expected") ErrTextFileExpected = errors.New("text file expected")
@ -16,9 +18,9 @@ var (
ErrEmptyNote = errors.New("empty note") ErrEmptyNote = errors.New("empty note")
ErrEncryptionRequired = errors.New("encryption is required") ErrEncryptionRequired = errors.New("encryption is required")
ErrClientEncryptionKeyNotAllowed = errors.New("client encryption key is not allowed") ErrClientEncryptionKeyNotAllowed = errors.New("client encryption key is not allowed")
ErrInvalidExpiration = errors.New("invalid expiration")
ErrCouldNotCreateNote = errors.New("could not create note") ErrCouldNotCreateNote = errors.New("could not create note")
ErrCouldNotDecodePayload = errors.New("could not decode payload") ErrCouldNotDecodePayload = errors.New("could not decode payload")
ErrCouldNotDecodeContent = errors.New("could not decode content") ErrCouldNotDecodeContent = errors.New("could not decode content")
ErrCouldNotDecodePassword = errors.New("could not decode password")
ErrNoteIsPasswordProtected = errors.New("note is password protected") ErrNoteIsPasswordProtected = errors.New("note is password protected")
) )

View file

@ -28,6 +28,8 @@ func apiError(level int, w http.ResponseWriter, logger *slog.Logger, topLevelErr
// Wrap error for logging // Wrap error for logging
if err != nil { if err != nil {
err = fmt.Errorf("%v: %w", topLevelErr, err) err = fmt.Errorf("%v: %w", topLevelErr, err)
} else {
err = topLevelErr
} }
logger.Error(fmt.Sprintf("%v", err)) 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) content, err := internal.Decode(body.Content)
if err != nil { if err != nil {
APIError(w, logger, ErrCouldNotDecodeContent, err) APIError(w, logger, ErrCouldNotDecodeContent, err)
return 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 { if err != nil {
APIError(w, logger, ErrCouldNotCreateNote, err) APIError(w, logger, ErrCouldNotCreateNote, err)
return return
@ -143,9 +150,9 @@ func (h *GetNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err != nil { if err != nil {
APIError(w, logger, ErrCouldNotFindNote, err) APIError(w, logger, ErrCouldNotFindNote, err)
} else if note == nil { } else if note == nil {
APIErrorNotFound(w, logger, ErrNoteDoesNotExist, err) APIErrorNotFound(w, logger, ErrNoteDoesNotExist, nil)
} else if note.PasswordHash != nil { } else if note.PasswordHash != nil {
APIErrorBadRequest(w, logger, ErrNoteIsPasswordProtected, err) APIErrorBadRequest(w, logger, ErrNoteIsPasswordProtected, nil)
} else { } else {
if note.Encrypted { if note.Encrypted {
w.Header().Set("Content-Type", "application/octet-stream") 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 { if len(note.PasswordHash) > 0 {
err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(body.Password)) err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(body.Password))
if err != nil { if err != nil {
APIError(w, logger, ErrInvalidPassword, err) APIErrorBadRequest(w, logger, ErrInvalidPassword, err)
return return
} }
} }

View file

@ -170,7 +170,7 @@ func (h *CreateNoteWithFormHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
} }
logger.Debug("saving note to the database") 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 { if err != nil {
h.WebError(w, logger, ErrCouldNotCreateNote, err) h.WebError(w, logger, ErrCouldNotCreateNote, err)
return return