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
|
@ -32,6 +32,7 @@ type NotePayload struct {
|
|||
Expiration int `json:"expiration,omitempty"`
|
||||
DeleteAfterRead bool `json:"delete_after_read,omitempty"`
|
||||
Language string `json:"language"`
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
type NoteResponse struct {
|
||||
|
@ -76,6 +77,7 @@ func handleMain() int {
|
|||
bearer := flag.String("bearer", os.Getenv("COLLER_BEARER"), "Bearer token")
|
||||
askBearer := flag.Bool("ask-bearer", false, "Read bearer token from input")
|
||||
language := flag.String("language", "", "Language of the note")
|
||||
password := flag.String("password", os.Getenv("COLLER_PASSWORD"), "Password to access the note")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
|
@ -172,6 +174,9 @@ func handleMain() int {
|
|||
if *language != "" {
|
||||
p.Language = *language
|
||||
}
|
||||
if *password != "" {
|
||||
p.Password = *password
|
||||
}
|
||||
|
||||
if *encryptionKey != "" {
|
||||
logger.Debug("validating encryption key")
|
||||
|
@ -242,21 +247,24 @@ func handleMain() int {
|
|||
logger.Debug("finding note location")
|
||||
var location string
|
||||
noteURL := *url + "/" + jsonBody.ID
|
||||
if *encryptionKey != "" {
|
||||
if *copier {
|
||||
location = fmt.Sprintf("copier -encryption-key %s %s", *encryptionKey, noteURL)
|
||||
} else {
|
||||
if *html {
|
||||
location = fmt.Sprintf("%s/%s.html", noteURL, *encryptionKey)
|
||||
} else {
|
||||
location = fmt.Sprintf("%s/%s", noteURL, *encryptionKey)
|
||||
}
|
||||
if *copier {
|
||||
location = "copier"
|
||||
if *encryptionKey != "" {
|
||||
location += " -encryption-key " + *encryptionKey
|
||||
}
|
||||
if *password != "" {
|
||||
location += " -password '" + *password + "'"
|
||||
}
|
||||
location += " " + noteURL
|
||||
} else {
|
||||
location = noteURL
|
||||
if *encryptionKey != "" {
|
||||
location += "/" + *encryptionKey
|
||||
}
|
||||
if *html {
|
||||
location = fmt.Sprintf("%s.html", noteURL)
|
||||
location += ".html"
|
||||
} else {
|
||||
location = noteURL
|
||||
location += "/raw"
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@ The file format is **JSON**:
|
|||
* **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
|
||||
* **enable_password_encryption** (bool): Enable password to protect notes (default true)
|
||||
* **enable_upload_file_button** (bool): Display the upload file button in the UI (default true)
|
||||
* **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)
|
||||
|
@ -35,7 +37,6 @@ The file format is **JSON**:
|
|||
* **observation_internal** (int): Number of seconds to wait between two observations (default 60)
|
||||
* **languages** ([]string): List of supported [languages](https://github.com/microsoft/monaco-editor/tree/main/src/basic-languages)
|
||||
* **language** (string): Default language (default "text")
|
||||
* **enable_upload_file_button** (bool): Display the upload file button in the UI (default true)
|
||||
* **tls_cert_file** (string): Path to TLS certificate file to enable HTTPS
|
||||
* **tls_key_file** (string): Path to TLS key file to enable HTTPS
|
||||
* **bootstrap_directory** (string): Serve [Bootstrap](https://getbootstrap.com/) assets from this local directory (ex: "./node_modules/bootstrap/dist"). See **Dependencies** for details.
|
||||
|
@ -64,19 +65,38 @@ Response (JSON):
|
|||
* **id** (string): ID of the note
|
||||
|
||||
|
||||
### GET /\<id\>/\<encryptionKey\>
|
||||
### GET /api/note/\<id\>/\<encryptionKey\>
|
||||
|
||||
> [!WARNING]
|
||||
> Potential encryption key leak
|
||||
|
||||
Return content of a note encrypted by the given encryption key.
|
||||
|
||||
### GET /\<id\>
|
||||
### POST /api/note/\<id\>/\<encryptionKey\>
|
||||
|
||||
> [!WARNING]
|
||||
> Potential encryption key leak
|
||||
|
||||
Return content of a protected note encrypted by the given encryption key.
|
||||
|
||||
Body (JSON):
|
||||
* **password** (string): password used to protect the note (required)
|
||||
|
||||
### GET /api/note/\<id\>
|
||||
|
||||
Return content of a note.
|
||||
|
||||
If the note is encrypted, the encrypted value is returned (application/octet-stream). Otherwise, the text is returned (text/plain).
|
||||
|
||||
### POST /api/note/\<id\>
|
||||
|
||||
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)
|
||||
|
||||
### Errors
|
||||
|
||||
Errors return **500 Server Internal Error** with the **JSON** payload:
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
|
@ -21,6 +24,10 @@ var (
|
|||
GitCommit string
|
||||
)
|
||||
|
||||
type NotePayload struct {
|
||||
Password string `json:"password"`
|
||||
}
|
||||
|
||||
func handleMain() int {
|
||||
|
||||
flag.Usage = usage
|
||||
|
@ -34,6 +41,7 @@ func handleMain() int {
|
|||
fileName := flag.String("file", "", "Write content of the note to a file")
|
||||
bearer := flag.String("bearer", os.Getenv("COLLER_BEARER"), "Bearer token")
|
||||
askBearer := flag.Bool("ask-bearer", false, "Read bearer token from input")
|
||||
password := flag.String("password", os.Getenv("COLLER_PASSWORD"), "Password to access the note")
|
||||
|
||||
flag.Parse()
|
||||
|
||||
|
@ -47,7 +55,7 @@ func handleMain() int {
|
|||
return internal.RC_ERROR
|
||||
}
|
||||
|
||||
url := flag.Args()[0]
|
||||
rawURL := flag.Args()[0]
|
||||
|
||||
var level slog.Level
|
||||
if *debug {
|
||||
|
@ -81,21 +89,50 @@ func handleMain() int {
|
|||
fmt.Print("\n")
|
||||
}
|
||||
|
||||
logger.Debug("creating http request")
|
||||
req, err := http.NewRequest("GET", url, nil)
|
||||
logger.Debug("parsing url", slog.Any("url", rawURL))
|
||||
u, err := url.Parse(rawURL)
|
||||
if err != nil {
|
||||
return internal.ReturnError(logger, "could not create request", err)
|
||||
return internal.ReturnError(logger, "could not parse url", err)
|
||||
}
|
||||
u.Path = "api/note" + u.Path
|
||||
|
||||
rawURL = u.String()
|
||||
|
||||
logger.Debug("creating http request")
|
||||
var req *http.Request
|
||||
if *password != "" {
|
||||
body := &NotePayload{
|
||||
Password: *password,
|
||||
}
|
||||
payload, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return internal.ReturnError(logger, "could not create note payload", err)
|
||||
}
|
||||
req, err = http.NewRequest("POST", rawURL, bytes.NewBuffer(payload))
|
||||
if err != nil {
|
||||
return internal.ReturnError(logger, "could not create request", err)
|
||||
}
|
||||
} else {
|
||||
req, err = http.NewRequest("GET", rawURL, nil)
|
||||
if err != nil {
|
||||
return internal.ReturnError(logger, "could not create request", err)
|
||||
}
|
||||
}
|
||||
|
||||
if *bearer != "" {
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *bearer))
|
||||
}
|
||||
|
||||
logger.Debug("parsing url", slog.Any("url", url))
|
||||
logger.Debug("executing http request", slog.Any("method", req.Method), slog.Any("url", rawURL))
|
||||
r, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue