1
0
Fork 0
forked from jriou/coller
coller/src/cmd/copier/main.go
Julien Riou ee7b5f0c6e
feat: Pass encryption key in URL fragment
- 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>
2025-10-01 12:43:04 +02:00

178 lines
4.2 KiB
Go

package main
import (
"bytes"
"encoding/json"
"flag"
"fmt"
"io"
"log/slog"
"net/http"
"net/url"
"os"
"syscall"
"golang.org/x/term"
"git.riou.xyz/jriou/coller/internal"
)
var (
AppName = "copier"
AppVersion string
GoVersion string
GitCommit string
)
type NotePayload struct {
Password string `json:"password"`
}
func handleMain() int {
flag.Usage = usage
version := flag.Bool("version", false, "Print version and exit")
quiet := flag.Bool("quiet", false, "Log errors only")
verbose := flag.Bool("verbose", false, "Print more logs")
debug := flag.Bool("debug", false, "Print even more logs")
encryptionKey := flag.String("encryption-key", os.Getenv("COLLER_ENCRYPTION_KEY"), "Key to decrypt the note")
askEncryptionKey := flag.Bool("ask-encryption-key", false, "Read encryption key from input")
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()
if *version {
internal.ShowVersion(AppName, AppVersion, GitCommit, GoVersion)
return internal.RC_OK
}
if flag.NArg() != 1 {
usage()
return internal.RC_ERROR
}
rawURL := flag.Args()[0]
var level slog.Level
if *debug {
level = slog.LevelDebug
}
if *verbose {
level = slog.LevelInfo
}
if *quiet {
level = slog.LevelError
}
logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: level}))
if *askEncryptionKey {
fmt.Print("Encryption key: ")
p, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return internal.ReturnError(logger, "could not read encryption key", err)
}
*encryptionKey = string(p)
fmt.Print("\n")
}
if *askBearer {
fmt.Print("Bearer: ")
b, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return internal.ReturnError(logger, "could not read bearer token", err)
}
*bearer = string(b)
fmt.Print("\n")
}
logger.Debug("parsing url", slog.Any("url", rawURL))
u, err := url.Parse(rawURL)
if err != nil {
return internal.ReturnError(logger, "could not parse url", err)
}
u.Path = "api/note" + u.Path
if u.Fragment != "" {
*encryptionKey = u.Fragment
u.Fragment = ""
}
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("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 {
return internal.ReturnError(logger, "could not read response", err)
}
var content []byte
if *encryptionKey != "" {
logger.Debug("decrypting note")
content, err = internal.Decrypt(body, *encryptionKey)
if err != nil {
return internal.ReturnError(logger, "could not decrypt note", err)
}
} else {
content = body
}
if *fileName != "" {
logger.Debug("writing output to file", slog.Any("file", *fileName))
err = os.WriteFile(*fileName, content, 0644)
if err != nil {
return internal.ReturnError(logger, "could not write output to file", err)
}
} else {
fmt.Printf("%s", content)
}
return internal.RC_OK
}
func usage() {
fmt.Printf("Usage: %s [OPTIONS] URL\n", os.Args[0])
flag.PrintDefaults()
}
func main() {
os.Exit(handleMain())
}