forked from jriou/coller
feat: Add password protection
Fixes #37. BREAKING CHANGE: API routes are prefixed by /api/note. Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
parent
61ca30690b
commit
9e0254c0b5
16 changed files with 713 additions and 135 deletions
|
@ -7,6 +7,7 @@ import (
|
|||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
||||
"git.riou.xyz/jriou/coller/internal"
|
||||
)
|
||||
|
@ -25,6 +26,7 @@ type CreateNoteHandler struct {
|
|||
|
||||
type CreateNotePayload struct {
|
||||
Content string `json:"content"`
|
||||
Password string `json:"password"`
|
||||
EncryptionKey string `json:"encryption_key"`
|
||||
Encrypted bool `json:"encrypted"`
|
||||
Expiration int `json:"expiration"`
|
||||
|
@ -66,7 +68,7 @@ func (h *CreateNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
note, err := h.db.Create(content, body.EncryptionKey, body.Encrypted, body.Expiration, body.DeleteAfterRead, body.Language)
|
||||
note, err := h.db.Create(content, body.Password, body.EncryptionKey, body.Encrypted, body.Expiration, body.DeleteAfterRead, body.Language)
|
||||
if err != nil {
|
||||
WriteError(w, "could not create note", err)
|
||||
return
|
||||
|
@ -92,6 +94,10 @@ func (h *GetNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
WriteError(w, "could not get note", err)
|
||||
} else if note == nil {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
h.logger.Error("note does not exists", slog.Any("note_id", id))
|
||||
} else if note.PasswordHash != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
h.logger.Error("note is password protected", slog.Any("note_id", note.ID))
|
||||
} else {
|
||||
if note.Encrypted {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
|
@ -101,17 +107,32 @@ func (h *GetNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
type GetEncryptedNoteHandler struct {
|
||||
logger *slog.Logger
|
||||
db *Database
|
||||
type GetProtectedNoteHandler struct {
|
||||
logger *slog.Logger
|
||||
db *Database
|
||||
maxUploadSize int64
|
||||
}
|
||||
|
||||
func (h *GetEncryptedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
type GetProtectedNotePayload struct {
|
||||
EncryptionKey string `json:"encryption_key"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func (h *GetProtectedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
|
||||
|
||||
vars := mux.Vars(r)
|
||||
id := vars["id"]
|
||||
encryptionKey := vars["encryptionKey"]
|
||||
|
||||
bodyReader := http.MaxBytesReader(w, r.Body, h.maxUploadSize)
|
||||
defer r.Body.Close()
|
||||
|
||||
var body GetProtectedNotePayload
|
||||
err := json.NewDecoder(bodyReader).Decode(&body)
|
||||
if err != nil {
|
||||
WriteError(w, "could not decode payload to read protected note", err)
|
||||
return
|
||||
}
|
||||
|
||||
note, err := h.db.Get(id)
|
||||
|
||||
|
@ -123,14 +144,22 @@ func (h *GetEncryptedNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Reque
|
|||
return
|
||||
}
|
||||
|
||||
if encryptionKey != "" && note.Encrypted {
|
||||
note.Content, err = internal.Decrypt(note.Content, encryptionKey)
|
||||
if body.EncryptionKey != "" && note.Encrypted {
|
||||
note.Content, err = internal.Decrypt(note.Content, body.EncryptionKey)
|
||||
if err != nil {
|
||||
WriteError(w, "could not decrypt note", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if body.Password != "" && (note.PasswordHash != nil || len(note.PasswordHash) > 0) {
|
||||
err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(body.Password))
|
||||
if err != nil {
|
||||
WriteError(w, "could not validate password", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
fmt.Fprint(w, string(note.Content))
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue