feat: Disable levels of encryptions by default
All checks were successful
/ pre-commit (push) Successful in 1m11s

- Add `allow_client_encryption_key` option to allow encryption key provided by
  the client on the web UI (false by default)
- Add `allow_no_encryption` option to allow notes without encryption (disabled
  by default)

Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
Julien Riou 2025-09-24 17:44:50 +02:00
commit 61ca30690b
Signed by: jriou
GPG key ID: 9A099EDA51316854
6 changed files with 105 additions and 47 deletions

View file

@ -20,6 +20,8 @@ The file format is **JSON**:
* **database_dsn** (string): Connection string for the database (default "collerd.db")
* **node_id** (int): Number between 0 and 1023 to define the node generating identifiers (see [snowflake](https://github.com/bwmarrin/snowflake))
* **encryption_key_length** (int): Number of characters for generated encryption key (default 16)
* **allow_client_encryption_key** (bool): Allow encryption key provided by the client on the web UI
* **allow_no_encryption** (bool): Allow notes without encryption
* **expiration_interval** (int): Number of seconds to wait between two expiration runs
* **listen_address** (string): Address to listen for the web server (default "0.0.0.0")
* **listen_port** (int): Port to listen for the web server (default 8080)

View file

@ -12,6 +12,8 @@ type Config struct {
DatabaseDsn string `json:"database_dsn"`
NodeID int64 `json:"node_id"`
EncryptionKeyLength int `json:"encryption_key_length"`
AllowClientEncryptionKey bool `json:"allow_client_encryption_key"`
AllowNoEncryption bool `json:"allow_no_encryption"`
ExpirationInterval int `json:"expiration_interval"`
ListenAddress string `json:"listen_address"`
ListenPort int `json:"listen_port"`

View file

@ -19,6 +19,8 @@ type CreateNoteHandler struct {
logger *slog.Logger
db *Database
maxUploadSize int64
allowClientEncryptionKey bool
allowNoEncryption bool
}
type CreateNotePayload struct {
@ -47,6 +49,16 @@ func (h *CreateNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
if !h.allowNoEncryption && !body.Encrypted {
WriteError(w, "could not create note", fmt.Errorf("encryption is mandatory"))
return
}
if !h.allowClientEncryptionKey && body.EncryptionKey != "" {
WriteError(w, "could not create note", fmt.Errorf("client encryption key is not allowed"))
return
}
content, err := internal.Decode(body.Content)
if err != nil {

View file

@ -26,6 +26,8 @@ type PageData struct {
URL string
Note *Note
EnableUploadFileButton bool
AllowClientEncryptionKey bool
AllowNoEncryption bool
BootstrapDirectory string
}
@ -115,7 +117,17 @@ func (h *CreateNoteWithFormHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
deleteAfterRead := r.FormValue("delete-after-read")
language := r.FormValue("language")
if encryptionKey == "" && noEncryption == "" {
if !h.PageData.AllowNoEncryption && noEncryption != "" {
h.PageData.Err = fmt.Errorf("encryption is mandatory")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
}
if !h.PageData.AllowClientEncryptionKey && encryptionKey != "" {
h.PageData.Err = fmt.Errorf("client encryption key is not allowed")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
}
if !h.PageData.AllowClientEncryptionKey && encryptionKey == "" && noEncryption == "" {
h.logger.Debug("generating encryption key")
encryptionKey = internal.GenerateChars(encryptionKeyLength)
}

View file

@ -99,9 +99,26 @@ func (s *Server) Start() error {
}
// API
r.Path("/api/note").Handler(&CreateNoteHandler{logger: s.logger, db: s.db, maxUploadSize: s.config.MaxUploadSize}).Methods("POST")
r.Path("/{id:[a-zA-Z0-9]+}/{encryptionKey:[a-zA-Z0-9]+}").Handler(&GetEncryptedNoteHandler{logger: s.logger, db: s.db}).Methods("GET")
r.Path("/{id:[a-zA-Z0-9]+}").Handler(&GetNoteHandler{logger: s.logger, db: s.db}).Methods("GET")
createNoteHandler := &CreateNoteHandler{
logger: s.logger,
db: s.db,
maxUploadSize: s.config.MaxUploadSize,
allowClientEncryptionKey: s.config.AllowClientEncryptionKey,
allowNoEncryption: s.config.AllowNoEncryption,
}
r.Path("/api/note").Handler(createNoteHandler).Methods("POST")
getEncryptedNoteHandler := &GetEncryptedNoteHandler{
logger: s.logger,
db: s.db,
}
r.Path("/{id:[a-zA-Z0-9]+}/{encryptionKey:[a-zA-Z0-9]+}").Handler(getEncryptedNoteHandler).Methods("GET")
getNoteHandler := &GetNoteHandler{
logger: s.logger,
db: s.db,
}
r.Path("/{id:[a-zA-Z0-9]+}").Handler(getNoteHandler).Methods("GET")
// Web pages
funcs := template.FuncMap{
@ -116,17 +133,20 @@ func (s *Server) Start() error {
Expiration: s.config.Expiration,
Languages: s.config.Languages,
BootstrapDirectory: s.config.BootstrapDirectory,
EnableUploadFileButton: s.config.EnableUploadFileButton,
AllowClientEncryptionKey: s.config.AllowClientEncryptionKey,
AllowNoEncryption: s.config.AllowNoEncryption,
}
if s.config.ShowVersion {
p.Version = s.version
}
p.EnableUploadFileButton = s.config.EnableUploadFileButton
templates, err := template.New("templates").Funcs(funcs).ParseFS(templatesFS, "templates/*.html")
if err != nil {
return err
}
createNoteWithFormHandler := &CreateNoteWithFormHandler{
Templates: templates,
PageData: p,
@ -142,7 +162,12 @@ func (s *Server) Start() error {
logger: s.logger,
}
r.Path("/clients.html").Handler(clientsHandler).Methods("GET")
r.Path("/clients/{os:[a-z]+}-{arch:[a-z0-9]+}/{clientName:[a-z]+}").Handler(&ClientHandler{logger: s.logger, version: p.Version}).Methods("GET")
clientHandler := &ClientHandler{
logger: s.logger,
version: p.Version,
}
r.Path("/clients/{os:[a-z]+}-{arch:[a-z0-9]+}/{clientName:[a-z]+}").Handler(clientHandler).Methods("GET")
encryptedWebNoteHandler := &GetEncryptedWebNoteHandler{
Templates: templates,

View file

@ -13,6 +13,7 @@
</div>
<div class="container text-center justify-content-center w-75 mb-4">
<div class="row align-items-center">
{{if .AllowClientEncryptionKey}}
<div class="col-1">
<label class="col-form-label col-form-label-sm" for="encryption-key">Encryption key</label>
</div>
@ -21,11 +22,14 @@
title="Letters and numbers with length from 16 to 256" class="form-control" id="encryption-key"
name="encryption-key">
</div>
{{end}}
{{if .AllowNoEncryption}}
<div class="col-1">
<input type="checkbox" class="form-check-input" for="no-encryption-key" id="no-encryption-key"
value="no-encryption-key" name="no-encryption-key">
<label class="col-form-label col-form-label-sm" for="no-encryption-key">No encryption</label>
</div>
{{end}}
<div class="col-1">
<input type="checkbox" class="form-check-input" for="delete-after-read" id="delete-after-read"
value="delete-after-read" name="delete-after-read">
@ -40,7 +44,8 @@
<select class="form-select" aria-label="Expiration" id="expiration" name="expiration">
<option disabled>Expiration</option>
{{range $exp := .Expirations}}
<option {{ if eq $exp $.Expiration }}selected="selected"{{end}} value="{{$exp}}">{{HumanDuration $exp}}</option>
<option {{ if eq $exp $.Expiration }}selected="selected" {{end}} value="{{$exp}}">
{{HumanDuration $exp}}</option>
{{end}}
</select>
</div>