feat: Pass encryption key in URL fragment
All checks were successful
/ pre-commit (push) Successful in 1m21s

- Remove encryptionKey from URL
- Use POST method to pass both password and encryption key
- Parse URL fragment to extract the encryption key from the web (using
  javascript) and from the CLI

Fixes #36.

Signed-off-by: Julien Riou <julien@riou.xyz>
This commit is contained in:
Julien Riou 2025-10-01 12:40:46 +02:00
commit ee7b5f0c6e
Signed by: jriou
GPG key ID: 9A099EDA51316854
8 changed files with 103 additions and 316 deletions

View file

@ -249,22 +249,20 @@ func handleMain() int {
noteURL := *url + "/" + fmt.Sprintf("%d", jsonBody.ID)
if *copier {
location = "copier"
if *encryptionKey != "" {
location += " -encryption-key " + *encryptionKey
}
if *password != "" {
location += " -password '" + *password + "'"
}
location += " " + noteURL
if *encryptionKey != "" {
location += "#" + *encryptionKey
}
} else {
location = noteURL
if *encryptionKey != "" {
location += "/" + *encryptionKey
}
if *html {
location += ".html"
} else {
location += "/raw"
}
if *encryptionKey != "" {
location += "#" + *encryptionKey
}
}

View file

@ -96,6 +96,11 @@ func handleMain() int {
}
u.Path = "api/note" + u.Path
if u.Fragment != "" {
*encryptionKey = u.Fragment
u.Fragment = ""
}
rawURL = u.String()
logger.Debug("creating http request")

View file

@ -160,9 +160,9 @@ func (h *CreateNoteWithFormHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
scheme = "https://"
}
h.PageData.URL = fmt.Sprintf("%s%s/%d", scheme, r.Host, note.ID)
h.PageData.URL = fmt.Sprintf("%s%s/%d.html", scheme, r.Host, note.ID)
if encryptionKey != "" {
h.PageData.URL += "/" + encryptionKey
h.PageData.URL += "#" + encryptionKey
}
h.logger.Debug("rendering page")
@ -197,17 +197,11 @@ func (h *GetRawWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
return
}
if note.Encrypted {
h.PageData.Err = fmt.Errorf("note is encrypted")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
h.PageData.Note = note
h.logger.Debug("rendering page")
if len(note.PasswordHash) > 0 {
if note.Encrypted || len(note.PasswordHash) > 0 {
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
@ -241,11 +235,12 @@ func (h *GetProtectedRawWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http
}
password := r.FormValue("password")
encryptionKey := r.FormValue("encryption-key")
note, err := h.db.Get(id)
if err != nil {
h.PageData.Err = fmt.Errorf("could not get raw note")
h.PageData.Err = fmt.Errorf("could not find note")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
@ -257,56 +252,11 @@ func (h *GetProtectedRawWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http
}
if note.Encrypted {
h.PageData.Err = fmt.Errorf("note is encrypted")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(password)); err != nil {
h.PageData.Err = fmt.Errorf("invalid password")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
h.PageData.Note = note
h.logger.Debug("rendering page")
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, string(note.Content))
}
type GetEncryptedRawWebNoteHandler struct {
Templates *template.Template
PageData PageData
logger *slog.Logger
db *Database
}
func (h *GetEncryptedRawWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.PageData.Err = nil
templateName := "unprotectedNote"
vars := mux.Vars(r)
id := vars["id"]
encryptionKey := vars["encryptionKey"]
note, err := h.db.Get(id)
if err != nil {
h.PageData.Err = fmt.Errorf("could not get raw note")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if note == nil {
h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if encryptionKey != "" && note.Encrypted {
if encryptionKey == "" {
h.PageData.Err = fmt.Errorf("encryption key not found")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
note.Content, err = internal.Decrypt(note.Content, encryptionKey)
if err != nil {
h.PageData.Err = fmt.Errorf("could not decrypt note")
@ -315,70 +265,9 @@ func (h *GetEncryptedRawWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http
}
}
h.PageData.Note = note
h.logger.Debug("rendering page")
if len(note.PasswordHash) > 0 {
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, string(note.Content))
}
type GetProtectedEncryptedRawWebNoteHandler struct {
Templates *template.Template
PageData PageData
logger *slog.Logger
db *Database
maxUploadSize int64
}
func (h *GetProtectedEncryptedRawWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.PageData.Err = nil
templateName := "protectedNote"
vars := mux.Vars(r)
id := vars["id"]
encryptionKey := vars["encryptionKey"]
h.logger.Debug("parsing multipart form")
err := r.ParseMultipartForm(h.maxUploadSize)
if err != nil {
h.PageData.Err = err
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
password := r.FormValue("password")
note, err := h.db.Get(id)
if err != nil {
h.PageData.Err = fmt.Errorf("could not get raw note")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if note == nil {
h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(password)); err != nil {
h.PageData.Err = fmt.Errorf("invalid password")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if encryptionKey != "" && note.Encrypted {
note.Content, err = internal.Decrypt(note.Content, encryptionKey)
if err != nil {
h.PageData.Err = fmt.Errorf("could not decrypt note")
if err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(password)); err != nil {
h.PageData.Err = fmt.Errorf("invalid password")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
@ -421,12 +310,6 @@ func (h *GetWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
if note.Encrypted {
h.PageData.Err = fmt.Errorf("note is encrypted")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
h.PageData.Note = note
h.logger.Debug("rendering page")
@ -457,11 +340,12 @@ func (h *GetProtectedWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
}
password := r.FormValue("password")
encryptionKey := r.FormValue("encryption-key")
note, err := h.db.Get(id)
if err != nil {
h.PageData.Err = err
h.PageData.Err = fmt.Errorf("could not find note")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
@ -473,15 +357,25 @@ func (h *GetProtectedWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
}
if note.Encrypted {
h.PageData.Err = fmt.Errorf("note is encrypted")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
if encryptionKey == "" {
h.PageData.Err = fmt.Errorf("encryption key not found")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
note.Content, err = internal.Decrypt(note.Content, encryptionKey)
if err != nil {
h.PageData.Err = fmt.Errorf("could not decrypt note")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
}
if err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(password)); err != nil {
h.PageData.Err = fmt.Errorf("invalid password")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
if len(note.PasswordHash) > 0 {
if err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(password)); err != nil {
h.PageData.Err = fmt.Errorf("invalid password")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
}
h.PageData.Note = note
@ -490,111 +384,6 @@ func (h *GetProtectedWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Re
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
}
type GetEncryptedWebNoteHandler struct {
Templates *template.Template
PageData PageData
logger *slog.Logger
db *Database
}
func (h *GetEncryptedWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.PageData.Err = nil
templateName := "unprotectedNote"
vars := mux.Vars(r)
id := vars["id"]
encryptionKey := vars["encryptionKey"]
note, err := h.db.Get(id)
if err != nil {
h.PageData.Err = err
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if note == nil {
h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if encryptionKey != "" && note.Encrypted {
note.Content, err = internal.Decrypt(note.Content, encryptionKey)
if err != nil {
h.PageData.Err = fmt.Errorf("could not decrypt note")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
}
h.PageData.Note = note
h.logger.Debug("rendering encrypted note web page")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
}
type GetProtectedEncryptedWebNoteHandler struct {
Templates *template.Template
PageData PageData
logger *slog.Logger
db *Database
maxUploadSize int64
}
func (h *GetProtectedEncryptedWebNoteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.PageData.Err = nil
templateName := "protectedNote"
vars := mux.Vars(r)
id := vars["id"]
encryptionKey := vars["encryptionKey"]
h.logger.Debug("parsing multipart form")
err := r.ParseMultipartForm(h.maxUploadSize)
if err != nil {
h.PageData.Err = err
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
password := r.FormValue("password")
note, err := h.db.Get(id)
if err != nil {
h.PageData.Err = err
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if note == nil {
h.PageData.Err = fmt.Errorf("note doesn't exist or has been deleted")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if err := bcrypt.CompareHashAndPassword(note.PasswordHash, []byte(password)); err != nil {
h.PageData.Err = fmt.Errorf("invalid password")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
if encryptionKey != "" && note.Encrypted {
note.Content, err = internal.Decrypt(note.Content, encryptionKey)
if err != nil {
h.PageData.Err = fmt.Errorf("could not decrypt note")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
return
}
}
h.PageData.Note = note
h.logger.Debug("rendering encrypted note web page")
h.Templates.ExecuteTemplate(w, templateName, h.PageData)
}
type ClientsHandler struct {
Templates *template.Template
PageData PageData

View file

@ -161,56 +161,6 @@ func (s *Server) Start() error {
}
r.Path("/clients/{os:[a-z]+}-{arch:[a-z0-9]+}/{clientName:[a-z]+}").Handler(clientHandler).Methods("GET")
encryptedWebNoteHandler := &GetEncryptedWebNoteHandler{
Templates: templates,
PageData: p,
logger: s.logger,
db: s.db,
}
r.Path("/{id:[a-zA-Z0-9]+}/{encryptionKey:[a-zA-Z0-9]+}.html").Handler(encryptedWebNoteHandler).Methods("GET")
protectedEncryptedWebNoteHandler := &GetProtectedEncryptedWebNoteHandler{
Templates: templates,
PageData: p,
logger: s.logger,
db: s.db,
maxUploadSize: s.config.MaxUploadSize,
}
r.Path("/{id:[a-zA-Z0-9]+}/{encryptionKey:[a-zA-Z0-9]+}.html").Handler(protectedEncryptedWebNoteHandler).Methods("POST")
encryptedRawWebNoteHandler := &GetEncryptedRawWebNoteHandler{
Templates: templates,
PageData: p,
logger: s.logger,
db: s.db,
}
r.Path("/{id:[a-zA-Z0-9]+}/{encryptionKey:[a-zA-Z0-9]+}/raw").Handler(encryptedRawWebNoteHandler).Methods("GET")
protectedEncryptedRawWebNoteHandler := &GetProtectedEncryptedRawWebNoteHandler{
Templates: templates,
PageData: p,
logger: s.logger,
db: s.db,
}
r.Path("/{id:[a-zA-Z0-9]+}/{encryptionKey:[a-zA-Z0-9]+}/raw").Handler(protectedEncryptedRawWebNoteHandler).Methods("POST")
rawWebNoteHandler := &GetRawWebNoteHandler{
Templates: templates,
PageData: p,
logger: s.logger,
db: s.db,
}
r.Path("/{id:[a-zA-Z0-9]+}/raw").Handler(rawWebNoteHandler).Methods("GET")
protectedRawWebNoteHandler := &GetProtectedRawWebNoteHandler{
Templates: templates,
PageData: p,
logger: s.logger,
db: s.db,
maxUploadSize: s.config.MaxUploadSize,
}
r.Path("/{id:[a-zA-Z0-9]+}/raw").Handler(protectedRawWebNoteHandler).Methods("POST")
webNoteHandler := &GetWebNoteHandler{
Templates: templates,
PageData: p,
@ -228,6 +178,23 @@ func (s *Server) Start() error {
}
r.Path("/{id:[a-zA-Z0-9]+}.html").Handler(protectedWebNoteHandler).Methods("POST")
rawWebNoteHandler := &GetRawWebNoteHandler{
Templates: templates,
PageData: p,
logger: s.logger,
db: s.db,
}
r.Path("/{id:[a-zA-Z0-9]+}").Handler(rawWebNoteHandler).Methods("GET")
protectedRawWebNoteHandler := &GetProtectedRawWebNoteHandler{
Templates: templates,
PageData: p,
logger: s.logger,
db: s.db,
maxUploadSize: s.config.MaxUploadSize,
}
r.Path("/{id:[a-zA-Z0-9]+}").Handler(protectedRawWebNoteHandler).Methods("POST")
if s.config.AceDirectory != "" {
r.PathPrefix("/static/ace-builds/").Handler(http.StripPrefix("/static/ace-builds/", http.FileServer(http.Dir(s.config.AceDirectory))))
}

View file

@ -8,18 +8,18 @@
{{block "header" .}}{{end}}
<div class="container mb-4 text-center">
{{if eq .Err nil}}
<div class="alert alert-success" role="alert">
<p>Note created successfully</p>
<p>
<a href="{{.URL}}.html">{{.URL}}.html</a>
</p>
</div>
{{else}}
{{if .Err}}
<div class="alert alert-danger" role="alert">
<p>Could not create note</p>
<p><strong>{{.Err}}</strong></p>
</div>
{{else}}
<div class="alert alert-success" role="alert">
<p>Note created successfully</p>
<p>
<a href="{{.URL}}">{{.URL}}</a>
</p>
</div>
{{end}}
</div>

View file

@ -8,7 +8,7 @@
<ul class="nav nav-pills align-items-center">
<li class="nav-item px-2">
<a href="" id="rawURL">raw</a>
<script>document.getElementById("rawURL").href = window.location.href.replace(".html", "/raw");</script>
<script>document.getElementById("rawURL").href = window.location.href.replace(".html", "");</script>
</li>
<li class="nav-item px-2">
{{.Note.Language}}

View file

@ -8,7 +8,7 @@
{{block "header" .}}{{end}}
{{if ne .Err nil}}
{{if .Err}}
{{block "error" .}}{{end}}
{{else}}
{{block "note" .}}{{end}}

View file

@ -7,12 +7,14 @@
<body>
{{block "header" .}}{{end}}
{{if ne .Err nil}}
{{if .Err}}
{{block "error" .}}{{end}}
{{else}}
{{else if or (gt (len .Note.PasswordHash) 0) .Note.Encrypted}}
<script>var protected = false;</script>
<div class="container mb-4">
{{if gt (len .Note.PasswordHash) 0}}
<form id="form" action="#" method="post" enctype="multipart/form-data">
<form id="form" method="post" enctype="multipart/form-data">
{{if gt (len .Note.PasswordHash) 0}}
<script>protected = true;</script>
<div class="container mb-4 w-25">
<div class="row text-center justify-content-center">
<label class="col-form-label" for="password">Password</label>
@ -23,9 +25,36 @@
<input type="password" class="form-control" id="password" name="password">
</div>
</div>
{{end}}
{{if .Note.Encrypted}}
<div id="encryption-container">
<div class="container mb-4 w-25">
<div class="row text-center justify-content-center">
<label class="col-form-label" for="password">Encryption key</label>
</div>
</div>
<div class="container mb-4 w-25">
<div class="row text-center justify-content-center">
<input type="password" pattern="^[a-zA-Z0-9]{16,256}$"
title="Letters and numbers with length from 16 to 256" class="form-control"
id="encryption-key" name="encryption-key">
</div>
</div>
</div>
<script>
var encryptionKey = window.location.hash.substr(1);
if (encryptionKey != "") {
document.getElementById("encryption-container").style.display = "none";
document.getElementById("encryption-key").value = encryptionKey;
if (!protected) {
document.getElementById("form").submit();
}
}
</script>
{{end}}
<div class="container mb-4 w-25">
<div class="row text-center justify-content-center">
<button type="submit" id="submit" class="btn btn-success">Submit</button>
<button type="btn-submit" id="btn-submit" class="btn btn-success">Submit</button>
</div>
</div>
</form>
@ -33,7 +62,6 @@
{{else}}
{{block "note" .}}{{end}}
{{end}}
{{end}}
{{block "footer" .}}{{end}}
</body>