From f41c5fde076b7f9aa0e1dfa68b00ee5a3ad4a40c Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 20 Oct 2023 02:47:35 +0200 Subject: [PATCH 01/24] clean(backend): Remove duplicate config example --- .env.example | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .env.example diff --git a/.env.example b/.env.example deleted file mode 100644 index faaaf55..0000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -PLAKKEN_HOST=0.0.0.0 -PLAKKEN_PORT=3000 -PLAKKEN_REDIS_ADDR=localhost:6379 -PLAKKEN_REDIS_USER= -PLAKKEN_REDIS_PASSWORD= -PLAKKEN_REDIS_DB=0 -- 2.43.4 From 1c086c5afbab3c38b7191a6beb12893c1250a25c Mon Sep 17 00:00:00 2001 From: hacki Date: Sun, 26 Nov 2023 00:00:53 +0100 Subject: [PATCH 02/24] Added line counter + some return optimization --- .idea/plakken.iml | 3 + .../0607f785dfa3c3861b3239f6723eb276d8056461 | 2 + .../0e6c9e46466623539d545903ef37e20d7d759ee3 | 0 .../387a88904b25e2d2817b6038db3c3570bff90d0f | 0 .../5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee | 0 .../8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d | 0 .../a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 | 0 .../bd1335292519bbb90a7518e3aee1539f47a6149f | 0 .../ea7b422455b8b60eb5b1123f19040f20653b36a4 | 0 .idea/sonarlint/issuestore/index.pb | 17 ++++ .../0607f785dfa3c3861b3239f6723eb276d8056461 | 0 .../0e6c9e46466623539d545903ef37e20d7d759ee3 | 0 .../387a88904b25e2d2817b6038db3c3570bff90d0f | 0 .../5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee | 0 .../8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d | 0 .../a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 | 0 .../bd1335292519bbb90a7518e3aee1539f47a6149f | 0 .../ea7b422455b8b60eb5b1123f19040f20653b36a4 | 0 .idea/sonarlint/securityhotspotstore/index.pb | 17 ++++ config.go | 8 +- db.go | 5 +- main.go | 37 ++++---- static/app.js | 31 +++++++ static/index.html | 89 ++++++++++--------- static/style.css | 38 +++++--- utils.go | 16 ++-- 26 files changed, 174 insertions(+), 89 deletions(-) create mode 100644 .idea/sonarlint/issuestore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 create mode 100644 .idea/sonarlint/issuestore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 create mode 100644 .idea/sonarlint/issuestore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f create mode 100644 .idea/sonarlint/issuestore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee create mode 100644 .idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d create mode 100644 .idea/sonarlint/issuestore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 create mode 100644 .idea/sonarlint/issuestore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f create mode 100644 .idea/sonarlint/issuestore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 create mode 100644 .idea/sonarlint/issuestore/index.pb create mode 100644 .idea/sonarlint/securityhotspotstore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 create mode 100644 .idea/sonarlint/securityhotspotstore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 create mode 100644 .idea/sonarlint/securityhotspotstore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f create mode 100644 .idea/sonarlint/securityhotspotstore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee create mode 100644 .idea/sonarlint/securityhotspotstore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d create mode 100644 .idea/sonarlint/securityhotspotstore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 create mode 100644 .idea/sonarlint/securityhotspotstore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f create mode 100644 .idea/sonarlint/securityhotspotstore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 create mode 100644 .idea/sonarlint/securityhotspotstore/index.pb diff --git a/.idea/plakken.iml b/.idea/plakken.iml index 7f4273e..f10277f 100644 --- a/.idea/plakken.iml +++ b/.idea/plakken.iml @@ -7,4 +7,7 @@ + + \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 b/.idea/sonarlint/issuestore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 new file mode 100644 index 0000000..cd39d2f --- /dev/null +++ b/.idea/sonarlint/issuestore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 @@ -0,0 +1,2 @@ + +rgo:S3776"RRefactor this method to reduce its Cognitive Complexity from 43 to the 15 allowed.(“Ó¬ãùÿÿÿÿ8üÚ¼ÅÀ1 \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 b/.idea/sonarlint/issuestore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f b/.idea/sonarlint/issuestore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee b/.idea/sonarlint/issuestore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d b/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 b/.idea/sonarlint/issuestore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f b/.idea/sonarlint/issuestore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 b/.idea/sonarlint/issuestore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/issuestore/index.pb b/.idea/sonarlint/issuestore/index.pb new file mode 100644 index 0000000..37ad8ce --- /dev/null +++ b/.idea/sonarlint/issuestore/index.pb @@ -0,0 +1,17 @@ + +5 +db.go,0\e\0e6c9e46466623539d545903ef37e20d7d759ee3 +8 +utils.go,a\2\a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 +7 +main.go,0\6\0607f785dfa3c3861b3239f6723eb276d8056461 += + static/app.js,5\c\5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee +9 + config.go,3\8\387a88904b25e2d2817b6038db3c3570bff90d0f +A +static/index.html,b\d\bd1335292519bbb90a7518e3aee1539f47a6149f +@ +static/style.css,e\a\ea7b422455b8b60eb5b1123f19040f20653b36a4 +9 + README.md,8\e\8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d \ No newline at end of file diff --git a/.idea/sonarlint/securityhotspotstore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 b/.idea/sonarlint/securityhotspotstore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 b/.idea/sonarlint/securityhotspotstore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f b/.idea/sonarlint/securityhotspotstore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee b/.idea/sonarlint/securityhotspotstore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d b/.idea/sonarlint/securityhotspotstore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 b/.idea/sonarlint/securityhotspotstore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f b/.idea/sonarlint/securityhotspotstore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 b/.idea/sonarlint/securityhotspotstore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 new file mode 100644 index 0000000..e69de29 diff --git a/.idea/sonarlint/securityhotspotstore/index.pb b/.idea/sonarlint/securityhotspotstore/index.pb new file mode 100644 index 0000000..37ad8ce --- /dev/null +++ b/.idea/sonarlint/securityhotspotstore/index.pb @@ -0,0 +1,17 @@ + +5 +db.go,0\e\0e6c9e46466623539d545903ef37e20d7d759ee3 +8 +utils.go,a\2\a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 +7 +main.go,0\6\0607f785dfa3c3861b3239f6723eb276d8056461 += + static/app.js,5\c\5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee +9 + config.go,3\8\387a88904b25e2d2817b6038db3c3570bff90d0f +A +static/index.html,b\d\bd1335292519bbb90a7518e3aee1539f47a6149f +@ +static/style.css,e\a\ea7b422455b8b60eb5b1123f19040f20653b36a4 +9 + README.md,8\e\8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d \ No newline at end of file diff --git a/config.go b/config.go index d9c0ab4..8630a3a 100644 --- a/config.go +++ b/config.go @@ -7,7 +7,7 @@ import ( "strconv" ) -type config struct { +type Config struct { host string port string redisAddr string @@ -17,7 +17,7 @@ type config struct { urlLength int } -func getConfig() config { +func GetConfig() Config { err := godotenv.Load() if err != nil { log.Fatalf("Error loading .env file: %v", err) @@ -42,7 +42,7 @@ func getConfig() config { log.Fatal("Invalid PLAKKEN_REDIS_URL_LEN") } - conf := config{ + return Config{ host: os.Getenv("PLAKKEN_INTERFACE"), port: port, redisAddr: redisAddr, @@ -51,6 +51,4 @@ func getConfig() config { redisDB: redisDB, urlLength: urlLen, } - - return conf } diff --git a/db.go b/db.go index 5dcdd03..d6b60b7 100644 --- a/db.go +++ b/db.go @@ -9,7 +9,7 @@ import ( var ctx = context.Background() -func connectDB() *redis.Client { +func ConnectDB() *redis.Client { localDb := redis.NewClient(&redis.Options{ Addr: currentConfig.redisAddr, Username: currentConfig.redisUser, @@ -40,8 +40,7 @@ func insertPaste(key string, content string, secret string, ttl time.Duration) { } func getContent(key string) string { - content := db.HGet(ctx, key, "content").Val() - return content + return db.HGet(ctx, key, "content").Val() } func deleteContent(key string) { diff --git a/main.go b/main.go index aa7c81c..c4cd240 100644 --- a/main.go +++ b/main.go @@ -10,7 +10,7 @@ import ( "strings" ) -var currentConfig config +var currentConfig Config var db *redis.Client type pasteView struct { @@ -21,16 +21,17 @@ type pasteView struct { func handleRequest(w http.ResponseWriter, r *http.Request) { path := r.URL.Path clearPath := strings.ReplaceAll(r.URL.Path, "/raw", "") + staticResource := "/static/" switch r.Method { case "GET": if path == "/" { http.ServeFile(w, r, "./static/index.html") - } else if strings.HasPrefix(path, "/static/") { + } else if strings.HasPrefix(path, staticResource) { fs := http.FileServer(http.Dir("./static")) - http.Handle("/static/", http.StripPrefix("/static/", fs)) + http.Handle(staticResource, http.StripPrefix(staticResource, fs)) } else { - if urlExist(clearPath) { + if UrlExist(clearPath) { if strings.HasSuffix(path, "/raw") { pasteContent := getContent(clearPath) w.Header().Set("Content-Type", "text/plain") @@ -56,8 +57,8 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { } case "POST": if path == "/" { - secret := generateSecret() - url := "/" + generateUrl() + secret := GenerateSecret() + url := "/" + GenerateUrl() content := r.FormValue("content") insertPaste(url, content, secret, -1) http.Redirect(w, r, url, http.StatusSeeOther) @@ -65,28 +66,26 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusMethodNotAllowed) } case "DELETE": - if strings.HasPrefix(path, "/delete") { - urlItem := strings.Split(path, "/") - if urlExist("/" + urlItem[2]) { - secret := r.URL.Query().Get("secret") - if verifySecret("/"+urlItem[2], secret) { - deleteContent("/" + urlItem[2]) - w.WriteHeader(http.StatusNoContent) - } else { - w.WriteHeader(http.StatusForbidden) + if UrlExist(path) { + secret := r.URL.Query().Get("secret") + if secret == db.HGet(ctx, path, "secret").Val() { + err := db.Del(ctx, path) + if err != nil { + log.Println(err) } + w.WriteHeader(http.StatusNoContent) } else { - w.WriteHeader(http.StatusNotFound) + w.WriteHeader(http.StatusForbidden) } } else { - w.WriteHeader(http.StatusMethodNotAllowed) + w.WriteHeader(http.StatusNotFound) } } } func main() { - db = connectDB() - currentConfig = getConfig() + db = ConnectDB() + currentConfig = GetConfig() listen := currentConfig.host + ":" + currentConfig.port http.HandleFunc("/", handleRequest) diff --git a/static/app.js b/static/app.js index e69de29..bef2c56 100644 --- a/static/app.js +++ b/static/app.js @@ -0,0 +1,31 @@ +const codeEditor = document.getElementById('content'); +const lineCounter = document.getElementById('lines'); + +let lineCountCache = 0; + +// Update line counter +function updateLineCounter() { + const lineCount = codeEditor.value.split('\n').length; + + if (lineCountCache !== lineCount) { + const outarr = Array.from({length: lineCount}, (_, index) => index + 1); + lineCounter.value = outarr.join('\n'); + } + + lineCountCache = lineCount; +} + +codeEditor.addEventListener('input', updateLineCounter); + +codeEditor.addEventListener('keydown', (e) => { + if (e.key === 'Tab') { + e.preventDefault(); + + const {value, selectionStart, selectionEnd} = codeEditor; + codeEditor.value = `${value.slice(0, selectionStart)}\t${value.slice(selectionEnd)}`; + codeEditor.setSelectionRange(selectionStart + 1, selectionStart + 1); + updateLineCounter(); + } +}); + +updateLineCounter(); diff --git a/static/index.html b/static/index.html index 10504d4..40eb943 100644 --- a/static/index.html +++ b/static/index.html @@ -9,50 +9,57 @@ New plak • Plakken +
- - - +
+ + +
+
+ + + +
\ No newline at end of file diff --git a/static/style.css b/static/style.css index d605146..4005e5a 100644 --- a/static/style.css +++ b/static/style.css @@ -2,7 +2,7 @@ --accent: #be0560; --background: #141414; --border: #333; - --text: #e2e2e2; + --text: #e9e9e9; } body { @@ -12,19 +12,35 @@ body { margin: 0; } +form { + display: flex; + flex-flow: row wrap; +} + button, textarea { background-color: inherit; border: none; + outline: none; + resize: none; } -textarea { - color: var(--text); - font: 14px/1.6 "JetBrains Mono", monospace; +#lines { + color: #999; + font: 400 14px/1.6 "JetBrains Mono", monospace; height: calc(100vh - 3rem); - outline: none; - padding: 1rem; - resize: none; - width: calc(100vw - 2rem); + overflow-y: hidden; + padding: 10px 0; + text-align: center; + user-select: none; + width: 26px; +} + +#content { + color: var(--text); + font: 400 14px/1.6 "JetBrains Mono", monospace; + height: calc(100vh - 3rem); + padding: 10px 10px 10px 6px; + width: calc(100vw - 42px); } pre { @@ -43,7 +59,7 @@ nav { ul { display: flex; flex-flow: row wrap; - gap: 2.6rem; + gap: 36px; list-style: none; margin: 0; padding: 0 1.9rem; @@ -60,7 +76,7 @@ input, select { color: var(--text); font-size: 15px; outline: none; - padding: 5px 6px; + padding: 6px 8px; transition: border .15s ease; } @@ -86,7 +102,7 @@ input:focus-visible, select:focus-visible { border: 2px solid var(--text); } -button:focus-visible{ +button:focus-visible { outline: none; } diff --git a/utils.go b/utils.go index 36305da..2b2753e 100644 --- a/utils.go +++ b/utils.go @@ -7,7 +7,7 @@ import ( mathrand "math/rand" ) -func generateUrl() string { +func GenerateUrl() string { listChars := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") b := make([]rune, currentConfig.urlLength) for i := range b { @@ -17,7 +17,7 @@ func generateUrl() string { return string(b) } -func generateSecret() string { +func GenerateSecret() string { key := make([]byte, 32) _, err := rand.Read(key) if err != nil { @@ -27,14 +27,10 @@ func generateSecret() string { return hex.EncodeToString(key) } -func urlExist(url string) bool { - exist := db.Exists(ctx, url).Val() - return exist == 1 +func UrlExist(url string) bool { + return db.Exists(ctx, url).Val() == 1 } -func verifySecret(url string, secret string) bool { - if secret == db.HGet(ctx, url, "secret").Val() { - return true - } - return false +func VerifySecret(url string, secret string) bool { + return secret == db.HGet(ctx, url, "secret").Val() } -- 2.43.4 From 4b53f0e1d97b0b5217634d5cac527124abe9177f Mon Sep 17 00:00:00 2001 From: hacki Date: Sun, 26 Nov 2023 00:08:47 +0100 Subject: [PATCH 03/24] Remove IDE specific files --- .idea/.gitignore | 6 ------ .idea/modules.xml | 8 -------- .idea/plakken.iml | 13 ------------- .../6/0607f785dfa3c3861b3239f6723eb276d8056461 | 2 -- .../e/0e6c9e46466623539d545903ef37e20d7d759ee3 | 0 .../8/387a88904b25e2d2817b6038db3c3570bff90d0f | 0 .../c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee | 0 .../e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d | 0 .../2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 | 0 .../d/bd1335292519bbb90a7518e3aee1539f47a6149f | 0 .../a/ea7b422455b8b60eb5b1123f19040f20653b36a4 | 0 .idea/sonarlint/issuestore/index.pb | 17 ----------------- .../6/0607f785dfa3c3861b3239f6723eb276d8056461 | 0 .../e/0e6c9e46466623539d545903ef37e20d7d759ee3 | 0 .../8/387a88904b25e2d2817b6038db3c3570bff90d0f | 0 .../c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee | 0 .../e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d | 0 .../2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 | 0 .../d/bd1335292519bbb90a7518e3aee1539f47a6149f | 0 .../a/ea7b422455b8b60eb5b1123f19040f20653b36a4 | 0 .idea/sonarlint/securityhotspotstore/index.pb | 17 ----------------- .idea/vcs.xml | 6 ------ 22 files changed, 69 deletions(-) delete mode 100644 .idea/.gitignore delete mode 100644 .idea/modules.xml delete mode 100644 .idea/plakken.iml delete mode 100644 .idea/sonarlint/issuestore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 delete mode 100644 .idea/sonarlint/issuestore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 delete mode 100644 .idea/sonarlint/issuestore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f delete mode 100644 .idea/sonarlint/issuestore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee delete mode 100644 .idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d delete mode 100644 .idea/sonarlint/issuestore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 delete mode 100644 .idea/sonarlint/issuestore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f delete mode 100644 .idea/sonarlint/issuestore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 delete mode 100644 .idea/sonarlint/issuestore/index.pb delete mode 100644 .idea/sonarlint/securityhotspotstore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 delete mode 100644 .idea/sonarlint/securityhotspotstore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 delete mode 100644 .idea/sonarlint/securityhotspotstore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f delete mode 100644 .idea/sonarlint/securityhotspotstore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee delete mode 100644 .idea/sonarlint/securityhotspotstore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d delete mode 100644 .idea/sonarlint/securityhotspotstore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 delete mode 100644 .idea/sonarlint/securityhotspotstore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f delete mode 100644 .idea/sonarlint/securityhotspotstore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 delete mode 100644 .idea/sonarlint/securityhotspotstore/index.pb delete mode 100644 .idea/vcs.xml diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 8bf4d45..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index b9c05c5..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/plakken.iml b/.idea/plakken.iml deleted file mode 100644 index f10277f..0000000 --- a/.idea/plakken.iml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 b/.idea/sonarlint/issuestore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 deleted file mode 100644 index cd39d2f..0000000 --- a/.idea/sonarlint/issuestore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 +++ /dev/null @@ -1,2 +0,0 @@ - -rgo:S3776"RRefactor this method to reduce its Cognitive Complexity from 43 to the 15 allowed.(“Ó¬ãùÿÿÿÿ8üÚ¼ÅÀ1 \ No newline at end of file diff --git a/.idea/sonarlint/issuestore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 b/.idea/sonarlint/issuestore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/issuestore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f b/.idea/sonarlint/issuestore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/issuestore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee b/.idea/sonarlint/issuestore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d b/.idea/sonarlint/issuestore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/issuestore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 b/.idea/sonarlint/issuestore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/issuestore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f b/.idea/sonarlint/issuestore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/issuestore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 b/.idea/sonarlint/issuestore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/issuestore/index.pb b/.idea/sonarlint/issuestore/index.pb deleted file mode 100644 index 37ad8ce..0000000 --- a/.idea/sonarlint/issuestore/index.pb +++ /dev/null @@ -1,17 +0,0 @@ - -5 -db.go,0\e\0e6c9e46466623539d545903ef37e20d7d759ee3 -8 -utils.go,a\2\a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 -7 -main.go,0\6\0607f785dfa3c3861b3239f6723eb276d8056461 -= - static/app.js,5\c\5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee -9 - config.go,3\8\387a88904b25e2d2817b6038db3c3570bff90d0f -A -static/index.html,b\d\bd1335292519bbb90a7518e3aee1539f47a6149f -@ -static/style.css,e\a\ea7b422455b8b60eb5b1123f19040f20653b36a4 -9 - README.md,8\e\8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d \ No newline at end of file diff --git a/.idea/sonarlint/securityhotspotstore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 b/.idea/sonarlint/securityhotspotstore/0/6/0607f785dfa3c3861b3239f6723eb276d8056461 deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/securityhotspotstore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 b/.idea/sonarlint/securityhotspotstore/0/e/0e6c9e46466623539d545903ef37e20d7d759ee3 deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/securityhotspotstore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f b/.idea/sonarlint/securityhotspotstore/3/8/387a88904b25e2d2817b6038db3c3570bff90d0f deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/securityhotspotstore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee b/.idea/sonarlint/securityhotspotstore/5/c/5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/securityhotspotstore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d b/.idea/sonarlint/securityhotspotstore/8/e/8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/securityhotspotstore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 b/.idea/sonarlint/securityhotspotstore/a/2/a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/securityhotspotstore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f b/.idea/sonarlint/securityhotspotstore/b/d/bd1335292519bbb90a7518e3aee1539f47a6149f deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/securityhotspotstore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 b/.idea/sonarlint/securityhotspotstore/e/a/ea7b422455b8b60eb5b1123f19040f20653b36a4 deleted file mode 100644 index e69de29..0000000 diff --git a/.idea/sonarlint/securityhotspotstore/index.pb b/.idea/sonarlint/securityhotspotstore/index.pb deleted file mode 100644 index 37ad8ce..0000000 --- a/.idea/sonarlint/securityhotspotstore/index.pb +++ /dev/null @@ -1,17 +0,0 @@ - -5 -db.go,0\e\0e6c9e46466623539d545903ef37e20d7d759ee3 -8 -utils.go,a\2\a2f5f17bf5a8e6bc481c55cb5ead6143c016d565 -7 -main.go,0\6\0607f785dfa3c3861b3239f6723eb276d8056461 -= - static/app.js,5\c\5cb1722284ad34a8a0c95e5f6c77e7c67c2a7eee -9 - config.go,3\8\387a88904b25e2d2817b6038db3c3570bff90d0f -A -static/index.html,b\d\bd1335292519bbb90a7518e3aee1539f47a6149f -@ -static/style.css,e\a\ea7b422455b8b60eb5b1123f19040f20653b36a4 -9 - README.md,8\e\8ec9a00bfd09b3190ac6b22251dbb1aa95a0579d \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file -- 2.43.4 From da1d1a1ef2e982638c0111dd4ee549c8453e2903 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 26 Nov 2023 02:53:24 +0100 Subject: [PATCH 04/24] fix(backend): broken utf8 on raw view --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index c4cd240..2fdec8a 100644 --- a/main.go +++ b/main.go @@ -34,7 +34,7 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { if UrlExist(clearPath) { if strings.HasSuffix(path, "/raw") { pasteContent := getContent(clearPath) - w.Header().Set("Content-Type", "text/plain") + w.Header().Set("Content-Type", "text/plain; charset=utf-8") _, err := io.WriteString(w, pasteContent) if err != nil { log.Println(err) -- 2.43.4 From d5610eb24c9d32266b60ce5076a83e8a26c09b61 Mon Sep 17 00:00:00 2001 From: Ada Date: Thu, 28 Dec 2023 00:02:50 +0100 Subject: [PATCH 05/24] :sparkles: Plak expiration support --- db.go | 2 +- main.go | 14 +++++++++++- static/index.html | 2 +- utils.go | 56 +++++++++++++++++++++++++++++++++++++++++++++++ utils_test.go | 43 ++++++++++++++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 utils_test.go diff --git a/db.go b/db.go index d6b60b7..a156541 100644 --- a/db.go +++ b/db.go @@ -35,7 +35,7 @@ func insertPaste(key string, content string, secret string, ttl time.Duration) { } err = db.HSet(ctx, key, "secret", hash.secret) if ttl > -1 { - db.Do(ctx, key, ttl) + db.Expire(ctx, key, ttl) } } diff --git a/main.go b/main.go index 2fdec8a..3064b46 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "log" "net/http" "strings" + "time" ) var currentConfig Config @@ -60,7 +61,18 @@ func handleRequest(w http.ResponseWriter, r *http.Request) { secret := GenerateSecret() url := "/" + GenerateUrl() content := r.FormValue("content") - insertPaste(url, content, secret, -1) + rawExpiration := r.FormValue("exp") + expiration, err := ParseExpiration(rawExpiration) + if err != nil { + log.Println(err) + } else if expiration == 0 { + insertPaste(url, content, secret, -1) + } else if expiration == -1 { + w.WriteHeader(http.StatusBadRequest) + } else { + insertPaste(url, content, secret, time.Duration(expiration*int(time.Second))) + } + http.Redirect(w, r, url, http.StatusSeeOther) } else { w.WriteHeader(http.StatusMethodNotAllowed) diff --git a/static/index.html b/static/index.html index 40eb943..dfaa289 100644 --- a/static/index.html +++ b/static/index.html @@ -27,7 +27,7 @@
  • -
  • +
  • + diff --git a/static/style.css b/static/style.css index 4005e5a..cf339b3 100644 --- a/static/style.css +++ b/static/style.css @@ -1,8 +1,9 @@ :root { --accent: #be0560; - --background: #141414; + --background: #121212; --border: #333; - --text: #e9e9e9; + --text: #e6e6e6; + --placeholder: #666; } body { @@ -17,6 +18,32 @@ form { flex-flow: row wrap; } +#lines { + color: var(--placeholder); + font: 400 14px/1.6 "JetBrains Mono", monospace; + height: calc(100vh - 29px); + overflow-y: hidden; + padding: 8px 0; + text-align: center; + user-select: none; + width: 26px; +} + +input, select { + background-color: var(--background); + border: 2px solid var(--border); + border-radius: 2px; + color: var(--text); + font-size: 13px; + outline: none; + padding: 6px 8px; + transition: border .15s ease; +} + +input:hover, select:hover { + border-color: var(--text); +} + button, textarea { background-color: inherit; border: none; @@ -24,36 +51,20 @@ button, textarea { resize: none; } -#lines { - color: #999; - font: 400 14px/1.6 "JetBrains Mono", monospace; - height: calc(100vh - 3rem); - overflow-y: hidden; - padding: 10px 0; - text-align: center; - user-select: none; - width: 26px; -} - #content { color: var(--text); font: 400 14px/1.6 "JetBrains Mono", monospace; - height: calc(100vh - 3rem); - padding: 10px 10px 10px 6px; - width: calc(100vw - 42px); -} - -pre { - margin: 15px; + height: calc(100vh - 29px); + padding: 8px; + width: calc(100vw - 45px); } nav { - align-items: end; - bottom: 1rem; + top: 1rem; display: flex; flex-flow: row wrap; position: absolute; - right: 1rem; + right: 12px; } ul { @@ -65,33 +76,12 @@ ul { padding: 0 1.9rem; } -label { - display: block; -} - -input, select { - background-color: var(--background); - border: 2px solid var(--border); - border-radius: 2px; - color: var(--text); - font-size: 15px; - outline: none; - padding: 6px 8px; - transition: border .15s ease; -} - -input:hover, select:hover { - border-color: var(--text); -} - svg { - fill: none; - height: 26px; - stroke: var(--text); - stroke-width: 2; - stroke-linecap: round; + cursor: pointer; + height: 24px; + fill: var(--text); transition: .15s ease; - width: 26px; + width: 24px; } svg:hover { @@ -102,10 +92,6 @@ input:focus-visible, select:focus-visible { border: 2px solid var(--text); } -button:focus-visible { - outline: none; -} - ::selection { background-color: var(--accent); color: #fff; diff --git a/test/utils/utils_test.go b/test/utils/utils_test.go new file mode 100644 index 0000000..1752121 --- /dev/null +++ b/test/utils/utils_test.go @@ -0,0 +1,81 @@ +package utils_test + +import ( + "errors" + "testing" + + "git.gnous.eu/gnouseu/plakken/internal/utils" +) + +func TestCheckCharNotRedundantTrue(t *testing.T) { // Test CheckCharRedundant with redundant char + want := true + got := utils.CheckCharRedundant("2d1h3md4h7s", "h") + if got != want { + t.Fatal("Error in parseExpirationFull, want : ", want, "got : ", got) + } +} + +func TestCheckCharNotRedundantFalse(t *testing.T) { // Test CheckCharRedundant with not redundant char + want := false + got := utils.CheckCharRedundant("2d1h3m47s", "h") + if got != want { + t.Fatal("Error in parseExpirationFull, want : ", want, "got : ", got) + } +} + +func TestParseExpirationFull(t *testing.T) { // test parseExpirationFull with all valid separator + result, _ := utils.ParseExpiration("2d1h3m47s") + correctValue := 176627 + if result != correctValue { + t.Fatal("Error in parseExpirationFull, want : ", correctValue, "got : ", result) + } +} + +func TestParseExpirationMissing(t *testing.T) { // test parseExpirationFull with all valid separator + result, _ := utils.ParseExpiration("1h47s") + correctValue := 3647 + if result != correctValue { + t.Fatal("Error in ParseExpirationFull, want : ", correctValue, "got : ", result) + } +} + +func TestParseExpirationWithCaps(t *testing.T) { // test parseExpirationFull with all valid separator + result, _ := utils.ParseExpiration("2D1h3M47s") + correctValue := 176627 + if result != correctValue { + t.Fatal("Error in parseExpirationFull, want : ", correctValue, "got : ", result) + } +} + +func TestParseExpirationNull(t *testing.T) { // test ParseExpirationFull with all valid separator + result, _ := utils.ParseExpiration("0") + correctValue := 0 + if result != correctValue { + t.Fatal("Error in ParseExpirationFull, want: ", correctValue, "got: ", result) + } +} + +func TestParseExpirationNegative(t *testing.T) { // test ParseExpirationFull with all valid separator + _, got := utils.ParseExpiration("-42h1m4s") + want := &utils.ParseExpirationError{} + if !errors.As(got, &want) { + t.Fatal("Error in ParseExpirationFull, want : ", want, "got : ", got) + } +} + +func TestParseExpirationInvalid(t *testing.T) { // test ParseExpirationFull with all valid separator + _, got := utils.ParseExpiration("8h42h1m1d4s") + want := &utils.ParseExpirationError{} + if !errors.As(got, &want) { + t.Fatal("Error in ParseExpirationFull, want : ", want, "got : ", got) + } + +} + +func TestParseExpirationInvalidRedundant(t *testing.T) { // test ParseExpirationFull with all valid separator + _, got := utils.ParseExpiration("8h42h1m1h4s") + want := &utils.ParseExpirationError{} + if !errors.As(got, &want) { + t.Fatal("Error in ParseExpirationFull, want : ", want, "got : ", got) + } +} diff --git a/utils_test.go b/utils_test.go deleted file mode 100644 index 4beac4c..0000000 --- a/utils_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import "testing" - -func TestParseExpirationFull(t *testing.T) { // test parseExpirationFull with all valid separator - result, _ := ParseExpiration("2d1h3m47s") - correctValue := 176627 - if result != correctValue { - t.Fatal("Error in parseExpirationFull, want : ", correctValue, "got : ", result) - } -} - -func TestParseExpirationMissing(t *testing.T) { // test parseExpirationFull with all valid separator - result, _ := ParseExpiration("1h47s") - correctValue := 3647 - if result != correctValue { - t.Fatal("Error in ParseExpirationFull, want : ", correctValue, "got : ", result) - } -} - -func TestParseExpirationNull(t *testing.T) { // test ParseExpirationFull with all valid separator - result, _ := ParseExpiration("0") - correctValue := 0 - if result != correctValue { - t.Fatal("Error in ParseExpirationFull, want : ", correctValue, "got : ", result) - } -} - -func TestParseExpirationNegative(t *testing.T) { // test ParseExpirationFull with all valid separator - result, _ := ParseExpiration("-42h1m4s") - correctValue := -1 - if result != correctValue { - t.Fatal("Error in ParseExpirationFull, want : ", correctValue, "got : ", result) - } -} - -func TestParseExpirationInvalid(t *testing.T) { // test ParseExpirationFull with all valid separator - result, _ := ParseExpiration("8h42h1m1d4s") - correctValue := -1 - if result != correctValue { - t.Fatal("Error in ParseExpirationFull, want : ", correctValue, "got : ", result) - } -} -- 2.43.4 From 93605560067fa4cedf3eed87d380310a75d8f883 Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 16 Feb 2024 22:41:29 +0100 Subject: [PATCH 09/24] :bug: fix 404 on first hit On first hit, Server() calls for the first time router(). As a result / and /static are only configured after first hit --- internal/httpServer/server.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/httpServer/server.go b/internal/httpServer/server.go index 3c4fa52..3d3ea96 100644 --- a/internal/httpServer/server.go +++ b/internal/httpServer/server.go @@ -17,7 +17,7 @@ type ServerConfig struct { } // Configure HTTP router -func (config ServerConfig) router(_ http.ResponseWriter, _ *http.Request) { +func (config ServerConfig) router() { WebConfig := plak.WebConfig{ DB: config.DB, UrlLength: config.UrlLength, @@ -45,7 +45,7 @@ func Config(listenAddress string) *http.Server { func (config ServerConfig) Server() { log.Println("Listening on " + config.HTTPServer.Addr) - http.HandleFunc("/", config.router) + config.router() log.Fatal(config.HTTPServer.ListenAndServe()) } -- 2.43.4 From 199a9264186185ec838881d34b0c72df31cc6896 Mon Sep 17 00:00:00 2001 From: Ada Date: Fri, 16 Feb 2024 22:36:13 +0100 Subject: [PATCH 10/24] :building_construction: Embed static files in binary --- internal/httpServer/server.go | 21 ++++++++++++++++++--- internal/web/plak/plak.go | 5 ++++- internal/web/static/static.go | 15 --------------- main.go | 11 +++++++++++ 4 files changed, 33 insertions(+), 19 deletions(-) delete mode 100644 internal/web/static/static.go diff --git a/internal/httpServer/server.go b/internal/httpServer/server.go index 3c4fa52..b5cb028 100644 --- a/internal/httpServer/server.go +++ b/internal/httpServer/server.go @@ -1,12 +1,12 @@ package httpServer import ( + "embed" "log" "net/http" "git.gnous.eu/gnouseu/plakken/internal/constant" "git.gnous.eu/gnouseu/plakken/internal/web/plak" - "git.gnous.eu/gnouseu/plakken/internal/web/static" "github.com/redis/go-redis/v9" ) @@ -14,6 +14,19 @@ type ServerConfig struct { HTTPServer *http.Server UrlLength uint8 DB *redis.Client + Static embed.FS + Templates embed.FS +} + +func (config ServerConfig) home(w http.ResponseWriter, r *http.Request) { + index, err := config.Static.ReadFile("static/index.html") + if err != nil { + log.Println(err) + } + _, err = w.Write(index) + if err != nil { + log.Println(err) + } } // Configure HTTP router @@ -21,11 +34,13 @@ func (config ServerConfig) router(_ http.ResponseWriter, _ *http.Request) { WebConfig := plak.WebConfig{ DB: config.DB, UrlLength: config.UrlLength, + Templates: config.Templates, } + staticFiles := http.FS(config.Static) - http.HandleFunc("GET /{$}", static.Home) + http.HandleFunc("GET /{$}", config.home) + http.Handle("GET /static/{file}", http.FileServer(staticFiles)) http.HandleFunc("GET /{key}/{settings...}", WebConfig.View) - http.HandleFunc("GET /static/{file}", static.ServeStatic) http.HandleFunc("POST /{$}", WebConfig.Create) http.HandleFunc("DELETE /{key}", WebConfig.Delete) } diff --git a/internal/web/plak/plak.go b/internal/web/plak/plak.go index d854a75..51b8af6 100644 --- a/internal/web/plak/plak.go +++ b/internal/web/plak/plak.go @@ -2,6 +2,7 @@ package plak import ( "context" + "embed" "io" "log" "net/http" @@ -19,6 +20,7 @@ var ctx = context.Background() type WebConfig struct { DB *redis.Client UrlLength uint8 + Templates embed.FS } // Plak "Object" for plak @@ -76,8 +78,9 @@ func (config WebConfig) View(w http.ResponseWriter, r *http.Request) { log.Println(err) } } else { - t, err := template.ParseFiles("templates/paste.html") + t, err := template.ParseFS(config.Templates, "templates/paste.html") if err != nil { + w.WriteHeader(http.StatusInternalServerError) log.Println(err) } err = t.Execute(w, plak) diff --git a/internal/web/static/static.go b/internal/web/static/static.go deleted file mode 100644 index 4c4a059..0000000 --- a/internal/web/static/static.go +++ /dev/null @@ -1,15 +0,0 @@ -package static - -import ( - "net/http" -) - -// ServeStatic Serve static file from static -func ServeStatic(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, "./static/"+r.PathValue("file")) // TODO: vérifier si c'est safe -} - -// Home Serve index.html -func Home(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, "./static/index.html") -} diff --git a/main.go b/main.go index 6ced2da..df25d23 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,20 @@ package main import ( + "embed" + "git.gnous.eu/gnouseu/plakken/internal/config" "git.gnous.eu/gnouseu/plakken/internal/database" "git.gnous.eu/gnouseu/plakken/internal/httpServer" ) +var ( + //go:embed templates + templates embed.FS + //go:embed static + static embed.FS +) + func main() { initConfig := config.GetConfig() dbConfig := database.InitDB(initConfig.RedisAddress, initConfig.RedisUser, initConfig.RedisPassword, initConfig.RedisDB) @@ -15,6 +24,8 @@ func main() { HTTPServer: httpServer.Config(initConfig.ListenAddress), UrlLength: initConfig.UrlLength, DB: db, + Static: static, + Templates: templates, } serverConfig.Server() -- 2.43.4 From 55a79dbd9d3a6067dc685d2544bf6f7b086c8651 Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 17 Feb 2024 00:32:55 +0100 Subject: [PATCH 11/24] :recycle: Simplifies return condition --- internal/utils/utils.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 77d41fd..e29556a 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -33,10 +33,7 @@ func GenerateSecret() string { // CheckCharRedundant verify is a character is redundant in a string func CheckCharRedundant(source string, char string) bool { // Verify if a char is redundant - if strings.Count(source, char) > 1 { - return true - } - return false + return strings.Count(source, char) > 1 } func parseIntBeforeSeparator(source *string, sep string) (int, error) { // return 0 & error if error, only accept positive number -- 2.43.4 From b5a9a3e669769eee9e2867264555a0f96ddf0fe6 Mon Sep 17 00:00:00 2001 From: Ada Date: Wed, 14 Feb 2024 18:57:27 +0100 Subject: [PATCH 12/24] :wrench: Supresss automatic .env load Automatic load of .env is a problem for Docker and not really usefull in other context --- go.mod | 5 +---- go.sum | 2 -- internal/config/config.go | 7 ------- 3 files changed, 1 insertion(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 042b6ec..0e58b75 100644 --- a/go.mod +++ b/go.mod @@ -2,10 +2,7 @@ module git.gnous.eu/gnouseu/plakken go 1.22 -require ( - github.com/joho/godotenv v1.5.1 - github.com/redis/go-redis/v9 v9.4.0 -) +require github.com/redis/go-redis/v9 v9.4.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/go.sum b/go.sum index 6c245cb..85f5563 100644 --- a/go.sum +++ b/go.sum @@ -6,7 +6,5 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk= github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= diff --git a/internal/config/config.go b/internal/config/config.go index 2576b54..373f03e 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -4,8 +4,6 @@ import ( "log" "os" "strconv" - - "github.com/joho/godotenv" ) // InitConfig Structure for program initialisation @@ -20,11 +18,6 @@ type InitConfig struct { // GetConfig Initialise configuration form .env func GetConfig() InitConfig { - err := godotenv.Load() - if err != nil { - log.Fatalf("Error loading .env file: %v", err) - } - listenAddress := os.Getenv("PLAKKEN_LISTEN") redisAddress := os.Getenv("PLAKKEN_REDIS_ADDRESS") db := os.Getenv("PLAKKEN_REDIS_DB") -- 2.43.4 From eaaf09c97878c2ad4245fa0d55df1b771e65d83c Mon Sep 17 00:00:00 2001 From: Ada Date: Wed, 14 Feb 2024 19:00:32 +0100 Subject: [PATCH 13/24] :construction_worker: Add docker support and CI related stuff --- .woodpecker/build.yaml | 32 ++++++++++++++++++++++++++++++ docker/Dockerfile | 28 ++++++++++++++++++++++++++ docker/docker-compose.dev.yaml | 31 +++++++++++++++++++++++++++++ docker/docker-compose.yaml | 36 ++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+) create mode 100644 .woodpecker/build.yaml create mode 100644 docker/Dockerfile create mode 100644 docker/docker-compose.dev.yaml create mode 100644 docker/docker-compose.yaml diff --git a/.woodpecker/build.yaml b/.woodpecker/build.yaml new file mode 100644 index 0000000..88083af --- /dev/null +++ b/.woodpecker/build.yaml @@ -0,0 +1,32 @@ +steps: + - name: publish_image + image: woodpeckerci/plugin-docker-buildx + settings: + repo: git.gnous.eu/${CI_REPO_OWNER}/plakken + dockerfile: docker/Dockerfile + platforms: linux/amd64,linux/arm64/v8,linux/arm + registry: https://git.gnous.eu + tag: ${CI_COMMIT} + username: + from_secret: docker_username + password: + from_secret: docker_password + when: + branch: ${CI_REPO_DEFAULT_BRANCH} + event: push + - name: publish_image_tag + image: woodpeckerci/plugin-docker-buildx + settings: + repo: git.gnous.eu/${CI_REPO_OWNER}/plakken + dockerfile: docker/Dockerfile + platforms: linux/amd64,linux/arm64/v8,linux/arm + registry: https://git.gnous.eu + tags: + - ${CI_COMMIT_TAG##v} # Remove v from tag + - stable + username: + from_secret: docker_username + password: + from_secret: docker_password + when: + event: tag diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..f50175c --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,28 @@ +# Build +FROM golang:1.22 AS build +LABEL authors="gnousEU" + +WORKDIR /build + +COPY go.mod go.sum ./ +RUN go mod download + +COPY main.go ./ +COPY internal/ ./internal +COPY static/ ./static +COPY templates/ ./templates + +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "-w -s" # Enable static binary, target Linux, remove debug information and strip binary + +# Copy to our image +FROM gcr.io/distroless/static-debian12:nonroot + +WORKDIR /app + +COPY --from=build /build/plakken ./ + +ENV PLAKKEN_LISTEN ":3000" + +EXPOSE 3000/tcp + +ENTRYPOINT ["/app/plakken"] diff --git a/docker/docker-compose.dev.yaml b/docker/docker-compose.dev.yaml new file mode 100644 index 0000000..8ec1e6e --- /dev/null +++ b/docker/docker-compose.dev.yaml @@ -0,0 +1,31 @@ +version: "3" + +networks: + plakken: + external: false + +services: + server: + build: + context: ../ + dockerfile: docker/Dockerfile + restart: always + container_name: plakken + networks: + - plakken + ports: + - "3000:3000" + environment: + - PLAKKEN_REDIS_ADDRESS=redis:6379 + - POSTGRES_PASSWORD=gitea + - PLAKKEN_REDIS_DB=0 + - PLAKKEN_URL_LENGTH=5 + depends_on: + - redis + redis: + image: redis:7-alpine + restart: always + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + networks: + - plakken diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml new file mode 100644 index 0000000..1dc30b0 --- /dev/null +++ b/docker/docker-compose.yaml @@ -0,0 +1,36 @@ +version: "3" + +networks: + plakken: + external: false + +volumes: + redis: + driver: local + +services: + server: + image: git.gnous.eu/gnouseu/plakken:latest + restart: always + container_name: plakken + read_only: true + networks: + - plakken + ports: + - "3000:3000" + environment: + - PLAKKEN_REDIS_ADDRESS=redis:6379 + - POSTGRES_PASSWORD=gitea + - PLAKKEN_REDIS_DB=0 + - PLAKKEN_URL_LENGTH=5 + depends_on: + - redis + redis: + image: redis:7-alpine + restart: always + healthcheck: + test: [ "CMD", "redis-cli", "ping" ] + networks: + - plakken + volumes: + - redis:/data -- 2.43.4 From 900243a7667062e3678fd1d8f18865c73c8f97a3 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 18 Feb 2024 20:32:07 +0100 Subject: [PATCH 14/24] :arrow_up: bump go-redis 9.5.0 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0e58b75..feff932 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module git.gnous.eu/gnouseu/plakken go 1.22 -require github.com/redis/go-redis/v9 v9.4.0 +require github.com/redis/go-redis/v9 v9.5.0 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/go.sum b/go.sum index 85f5563..b102a8f 100644 --- a/go.sum +++ b/go.sum @@ -6,5 +6,5 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk= -github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.5.0 h1:Xe9TKMmZv939gwTBcvc0n1tzK5l2re0pKw/W/tN3amw= +github.com/redis/go-redis/v9 v9.5.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= -- 2.43.4 From 6905f63059a53e6a165b0e184a5127e36795c4b7 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 18 Feb 2024 22:27:25 +0100 Subject: [PATCH 15/24] :construction_worker: Add build releases file on tag --- .woodpecker/release.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .woodpecker/release.yaml diff --git a/.woodpecker/release.yaml b/.woodpecker/release.yaml new file mode 100644 index 0000000..9c36d4b --- /dev/null +++ b/.woodpecker/release.yaml @@ -0,0 +1,24 @@ +steps: + - name: Build + image: golang:1.22 + commands: + - go mod download + - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-w -s" -o plakken-linux-amd64 # Enable static binary, target Linux, remove debug information and strip binary + - CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-w -s" -o plakken-linux-arm64 + - CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "-w -s" -o plakken-linux-arm + - CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags "-w -s" -o plakken-windows-amd64.exe + - CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags "-w -s" -o plakken-windows-arm64.exe + - CGO_ENABLED=0 GOOS=linux GOARCH=arm go build -ldflags "-w -s" -o plakken-windows-arm.exe + when: + event: tag + - name: Release + image: woodpeckerci/plugin-gitea-release + settings: + base_url: https://git.gnous.eu + files: + - "plakken*" + api_key: + from_secret: release_token + target: main + when: + event: tag \ No newline at end of file -- 2.43.4 From 36e959f5e2d589859c613ac8b0a134ebaf6cc967 Mon Sep 17 00:00:00 2001 From: Ada Date: Mon, 19 Feb 2024 00:32:27 +0100 Subject: [PATCH 16/24] :recycle: Use math/rand/v2 --- internal/utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index e29556a..fe3369d 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -4,7 +4,7 @@ import ( "crypto/rand" "encoding/hex" "log" - mathrand "math/rand" + mathrand "math/rand/v2" "strconv" "strings" ) @@ -14,7 +14,7 @@ func GenerateUrl(length uint8) string { listChars := []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789") b := make([]rune, length) for i := range b { - b[i] = listChars[mathrand.Intn(len(listChars))] + b[i] = listChars[mathrand.IntN(len(listChars))] } return string(b) -- 2.43.4 From 9570e339a39aacd0bed635208e94adc59f14fb4c Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 18 Feb 2024 23:27:14 +0100 Subject: [PATCH 17/24] :sparkles: Support filename --- internal/utils/utils.go | 11 +++++++++++ internal/web/plak/plak.go | 20 +++++++++++++++++++- test/utils/utils_test.go | 18 ++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index fe3369d..e3a98b1 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -5,6 +5,7 @@ import ( "encoding/hex" "log" mathrand "math/rand/v2" + "regexp" "strconv" "strings" ) @@ -99,3 +100,13 @@ func ParseExpiration(source string) (int, error) { return expiration, nil } + +// ValidKey Verify if a key is valid (only letter, number, - and _) +func ValidKey(key string) bool { + result, err := regexp.MatchString("^[a-zA-Z0-9_.-]*$", key) + if err != nil { + return false + } + log.Println(key, result) + return result +} diff --git a/internal/web/plak/plak.go b/internal/web/plak/plak.go index 51b8af6..f7fbe7c 100644 --- a/internal/web/plak/plak.go +++ b/internal/web/plak/plak.go @@ -36,18 +36,36 @@ func (config WebConfig) Create(w http.ResponseWriter, r *http.Request) { content := r.FormValue("content") if content == "" { w.WriteHeader(http.StatusBadRequest) + return } dbConf := database.DBConfig{ DB: config.DB, } + filename := r.FormValue("filename") + var key string + if len(filename) == 0 { + key = utils.GenerateUrl(config.UrlLength) + } else { + if !utils.ValidKey(filename) { + w.WriteHeader(http.StatusBadRequest) + return + } + if dbConf.UrlExist(filename) { + w.WriteHeader(http.StatusBadRequest) + return + } + key = filename + } + secret := utils.GenerateSecret() - key := utils.GenerateUrl(config.UrlLength) rawExpiration := r.FormValue("exp") + expiration, err := utils.ParseExpiration(rawExpiration) if err != nil { w.WriteHeader(http.StatusBadRequest) + return } else if expiration == 0 { dbConf.InsertPaste(key, content, secret, -1) } else { diff --git a/test/utils/utils_test.go b/test/utils/utils_test.go index 1752121..f2a87b6 100644 --- a/test/utils/utils_test.go +++ b/test/utils/utils_test.go @@ -79,3 +79,21 @@ func TestParseExpirationInvalidRedundant(t *testing.T) { // test ParseExpiration t.Fatal("Error in ParseExpirationFull, want : ", want, "got : ", got) } } + +func TestValidKey(t *testing.T) { // test ValidKey with a valid key + got := utils.ValidKey("ab_a-C42") + want := true + + if got != want { + t.Fatal("Error in ValidKey, want : ", want, "got : ", got) + } +} + +func TestInValidKey(t *testing.T) { // test ValidKey with invalid key + got := utils.ValidKey("ab_?a-C42") + want := false + + if got != want { + t.Fatal("Error in ValidKey, want : ", want, "got : ", got) + } +} -- 2.43.4 From 130a5ee345c7e0f30496e5fe982c36add6379c32 Mon Sep 17 00:00:00 2001 From: Ada Date: Wed, 21 Feb 2024 14:22:40 +0100 Subject: [PATCH 18/24] Add redis connection test before start --- internal/database/db.go | 9 +++++++++ internal/database/error.go | 7 +++++++ main.go | 5 +++++ 3 files changed, 21 insertions(+) create mode 100644 internal/database/error.go diff --git a/internal/database/db.go b/internal/database/db.go index 1769dc0..6f1e6fc 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -32,6 +32,15 @@ func ConnectDB(dbConfig *redis.Options) *redis.Client { return localDb } +// Ping test connection to Redis database +func Ping(db *redis.Client) error { + status := db.Ping(ctx) + if status.String() != "ping: PONG" { + return &PingError{} + } + return nil +} + func (config DBConfig) InsertPaste(key string, content string, secret string, ttl time.Duration) { type dbSchema struct { content string diff --git a/internal/database/error.go b/internal/database/error.go new file mode 100644 index 0000000..3985065 --- /dev/null +++ b/internal/database/error.go @@ -0,0 +1,7 @@ +package database + +type PingError struct{} + +func (m *PingError) Error() string { + return "Connection to redis not work" +} diff --git a/main.go b/main.go index df25d23..8af2291 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "embed" + "log" "git.gnous.eu/gnouseu/plakken/internal/config" "git.gnous.eu/gnouseu/plakken/internal/database" @@ -19,6 +20,10 @@ func main() { initConfig := config.GetConfig() dbConfig := database.InitDB(initConfig.RedisAddress, initConfig.RedisUser, initConfig.RedisPassword, initConfig.RedisDB) db := database.ConnectDB(dbConfig) + err := database.Ping(db) + if err != nil { + log.Fatal(err) + } serverConfig := httpServer.ServerConfig{ HTTPServer: httpServer.Config(initConfig.ListenAddress), -- 2.43.4 From e99c71cc20ccf1877863542c55fc989375c7bd3d Mon Sep 17 00:00:00 2001 From: Ada Date: Sat, 24 Feb 2024 01:26:27 +0100 Subject: [PATCH 19/24] :sparkles: Implement basic POST request for curl / can now read body and create paste, for curl like client, web create with full settings is move to /create --- internal/constant/constants.go | 3 ++- internal/httpServer/server.go | 5 +++-- internal/web/plak/plak.go | 39 +++++++++++++++++++++++++++++++++- static/index.html | 2 +- 4 files changed, 44 insertions(+), 5 deletions(-) diff --git a/internal/constant/constants.go b/internal/constant/constants.go index 0522e19..b99a4ce 100644 --- a/internal/constant/constants.go +++ b/internal/constant/constants.go @@ -3,5 +3,6 @@ package constant import "time" const ( - HTTPTimeout = 3 * time.Second + HTTPTimeout = 3 * time.Second + ExpirationCurlCreate = 604800 * time.Second // Second in one week ) diff --git a/internal/httpServer/server.go b/internal/httpServer/server.go index b2910d0..38976dc 100644 --- a/internal/httpServer/server.go +++ b/internal/httpServer/server.go @@ -18,7 +18,7 @@ type ServerConfig struct { Templates embed.FS } -func (config ServerConfig) home(w http.ResponseWriter, r *http.Request) { +func (config ServerConfig) home(w http.ResponseWriter, _ *http.Request) { index, err := config.Static.ReadFile("static/index.html") if err != nil { log.Println(err) @@ -41,7 +41,8 @@ func (config ServerConfig) router() { http.HandleFunc("GET /{$}", config.home) http.Handle("GET /static/{file}", http.FileServer(staticFiles)) http.HandleFunc("GET /{key}/{settings...}", WebConfig.View) - http.HandleFunc("POST /{$}", WebConfig.Create) + http.HandleFunc("POST /{$}", WebConfig.CurlCreate) + http.HandleFunc("POST /create/{$}", WebConfig.Create) http.HandleFunc("DELETE /{key}", WebConfig.Delete) } diff --git a/internal/web/plak/plak.go b/internal/web/plak/plak.go index f7fbe7c..b80c137 100644 --- a/internal/web/plak/plak.go +++ b/internal/web/plak/plak.go @@ -8,6 +8,7 @@ import ( "net/http" "time" + "git.gnous.eu/gnouseu/plakken/internal/constant" "git.gnous.eu/gnouseu/plakken/internal/database" "git.gnous.eu/gnouseu/plakken/internal/utils" "github.com/redis/go-redis/v9" @@ -72,7 +73,41 @@ func (config WebConfig) Create(w http.ResponseWriter, r *http.Request) { dbConf.InsertPaste(key, content, secret, time.Duration(expiration*int(time.Second))) } - http.Redirect(w, r, key, http.StatusSeeOther) + http.Redirect(w, r, "/"+key, http.StatusSeeOther) +} + +// CurlCreate Create plak with minimum param, ideal for curl. Force 7 day expiration +func (config WebConfig) CurlCreate(w http.ResponseWriter, r *http.Request) { + if r.ContentLength == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + content, _ := io.ReadAll(r.Body) + err := r.Body.Close() + if err != nil { + log.Println(err) + } + key := utils.GenerateUrl(config.UrlLength) + secret := utils.GenerateSecret() + dbConf := database.DBConfig{ + DB: config.DB, + } + dbConf.InsertPaste(key, string(content), secret, constant.ExpirationCurlCreate) + + var baseURL string + if r.TLS == nil { + baseURL = "http://" + r.Host + "/" + key + } else { + baseURL = "https://" + r.Host + "/" + key + } + + message := baseURL + "\n" + "Delete with : 'curl -X DELETE " + baseURL + "?secret\\=" + secret + "'" + "\n" + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + _, err = io.WriteString(w, message) + if err != nil { + log.Println(err) + } } // View for plak @@ -130,8 +165,10 @@ func (config WebConfig) Delete(w http.ResponseWriter, r *http.Request) { log.Println(err) } w.WriteHeader(http.StatusNoContent) + return } else { w.WriteHeader(http.StatusForbidden) + return } } w.WriteHeader(http.StatusNotFound) diff --git a/static/index.html b/static/index.html index 5061a27..8459130 100644 --- a/static/index.html +++ b/static/index.html @@ -11,7 +11,7 @@ -
    +
    -- 2.43.4 From cdb738655893d78b848a4bf42badae2ca9c7bdef Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 25 Feb 2024 00:32:26 +0100 Subject: [PATCH 20/24] :arrow_up: bump go-redis 9.5.1 --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index feff932..892eee6 100644 --- a/go.mod +++ b/go.mod @@ -2,7 +2,7 @@ module git.gnous.eu/gnouseu/plakken go 1.22 -require github.com/redis/go-redis/v9 v9.5.0 +require github.com/redis/go-redis/v9 v9.5.1 require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect diff --git a/go.sum b/go.sum index b102a8f..a341487 100644 --- a/go.sum +++ b/go.sum @@ -6,5 +6,5 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= -github.com/redis/go-redis/v9 v9.5.0 h1:Xe9TKMmZv939gwTBcvc0n1tzK5l2re0pKw/W/tN3amw= -github.com/redis/go-redis/v9 v9.5.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= +github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= -- 2.43.4 From 3954079b95534adda58c5f1c1602dd34802328ca Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 25 Feb 2024 00:35:11 +0100 Subject: [PATCH 21/24] :test_tube: Add test to ParseExpiration when a value is to high --- test/utils/utils_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test/utils/utils_test.go b/test/utils/utils_test.go index f2a87b6..3525443 100644 --- a/test/utils/utils_test.go +++ b/test/utils/utils_test.go @@ -80,6 +80,14 @@ func TestParseExpirationInvalidRedundant(t *testing.T) { // test ParseExpiration } } +func TestParseExpirationInvalidTooHigh(t *testing.T) { // test ParseExpirationFull with all valid separator + _, got := utils.ParseExpiration("2d1h3m130s") + want := &utils.ParseExpirationError{} + if !errors.As(got, &want) { + t.Fatal("Error in ParseExpirationFull, want : ", want, "got : ", got) + } +} + func TestValidKey(t *testing.T) { // test ValidKey with a valid key got := utils.ValidKey("ab_a-C42") want := true -- 2.43.4 From f6b48d0ae7974a5f51ba7a5eca1382594ed455b6 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 25 Feb 2024 00:29:39 +0100 Subject: [PATCH 22/24] lock: Add secret token hashing with argon2id --- go.mod | 6 +- go.sum | 4 + internal/constant/constants.go | 6 ++ internal/database/db.go | 14 +++- internal/database/error.go | 4 +- internal/httpServer/server.go | 4 +- internal/secret/crypto.go | 147 +++++++++++++++++++++++++++++++++ internal/secret/error.go | 9 ++ internal/utils/error.go | 12 +-- internal/utils/utils.go | 29 ++----- internal/web/plak/error.go | 18 ++-- internal/web/plak/plak.go | 139 ++++++++++++++++++++++--------- test/secret/secret_test.go | 47 +++++++++++ 13 files changed, 359 insertions(+), 80 deletions(-) create mode 100644 internal/secret/crypto.go create mode 100644 internal/secret/error.go create mode 100644 test/secret/secret_test.go diff --git a/go.mod b/go.mod index 892eee6..a60a2d0 100644 --- a/go.mod +++ b/go.mod @@ -2,9 +2,13 @@ module git.gnous.eu/gnouseu/plakken go 1.22 -require github.com/redis/go-redis/v9 v9.5.1 +require ( + github.com/redis/go-redis/v9 v9.5.1 + golang.org/x/crypto v0.20.0 +) require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + golang.org/x/sys v0.17.0 // indirect ) diff --git a/go.sum b/go.sum index a341487..899f6d8 100644 --- a/go.sum +++ b/go.sum @@ -8,3 +8,7 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/internal/constant/constants.go b/internal/constant/constants.go index b99a4ce..ae9e4d5 100644 --- a/internal/constant/constants.go +++ b/internal/constant/constants.go @@ -5,4 +5,10 @@ import "time" const ( HTTPTimeout = 3 * time.Second ExpirationCurlCreate = 604800 * time.Second // Second in one week + TokenLength = 32 + ArgonSaltSize = 16 + ArgonMemory = 64 * 1024 + ArgonThreads = 4 + ArgonKeyLength = 32 + ArgonIterations = 2 ) diff --git a/internal/database/db.go b/internal/database/db.go index 6f1e6fc..8c5ec68 100644 --- a/internal/database/db.go +++ b/internal/database/db.go @@ -5,6 +5,7 @@ import ( "log" "time" + "git.gnous.eu/gnouseu/plakken/internal/secret" "github.com/redis/go-redis/v9" ) @@ -36,7 +37,7 @@ func ConnectDB(dbConfig *redis.Options) *redis.Client { func Ping(db *redis.Client) error { status := db.Ping(ctx) if status.String() != "ping: PONG" { - return &PingError{} + return &pingError{} } return nil } @@ -68,6 +69,13 @@ func (config DBConfig) UrlExist(url string) bool { return config.DB.Exists(ctx, url).Val() == 1 } -func (config DBConfig) VerifySecret(url string, secret string) bool { - return secret == config.DB.HGet(ctx, url, "secret").Val() +func (config DBConfig) VerifySecret(url string, token string) (bool, error) { + storedHash := config.DB.HGet(ctx, url, "secret").Val() + + result, err := secret.VerifyPassword(token, storedHash) + if err != nil { + return false, err + } + + return result, nil } diff --git a/internal/database/error.go b/internal/database/error.go index 3985065..de0951e 100644 --- a/internal/database/error.go +++ b/internal/database/error.go @@ -1,7 +1,7 @@ package database -type PingError struct{} +type pingError struct{} -func (m *PingError) Error() string { +func (m *pingError) Error() string { return "Connection to redis not work" } diff --git a/internal/httpServer/server.go b/internal/httpServer/server.go index 38976dc..f17ea63 100644 --- a/internal/httpServer/server.go +++ b/internal/httpServer/server.go @@ -42,8 +42,8 @@ func (config ServerConfig) router() { http.Handle("GET /static/{file}", http.FileServer(staticFiles)) http.HandleFunc("GET /{key}/{settings...}", WebConfig.View) http.HandleFunc("POST /{$}", WebConfig.CurlCreate) - http.HandleFunc("POST /create/{$}", WebConfig.Create) - http.HandleFunc("DELETE /{key}", WebConfig.Delete) + http.HandleFunc("POST /create/{$}", WebConfig.PostCreate) + http.HandleFunc("DELETE /{key}", WebConfig.DeleteRequest) } // Config Configure HTTP server diff --git a/internal/secret/crypto.go b/internal/secret/crypto.go new file mode 100644 index 0000000..957c260 --- /dev/null +++ b/internal/secret/crypto.go @@ -0,0 +1,147 @@ +// Package secret implement all crypto utils like password hashing and secret generation +package secret + +import ( + "bytes" + "crypto/rand" + "encoding/base64" + "fmt" + "strconv" + "strings" + + "git.gnous.eu/gnouseu/plakken/internal/constant" + "golang.org/x/crypto/argon2" +) + +type argon2idHash struct { + salt []byte + hash []byte +} + +// Argon2id config +type config struct { + saltLength uint8 + memory uint32 + threads uint8 + keyLength uint32 + iterations uint32 +} + +// generateSecret for password hashing or token generation +func generateSecret(length uint8) ([]byte, error) { + secret := make([]byte, length) + + _, err := rand.Read(secret) + if err != nil { + return nil, err + } + + return secret, err +} + +// GenerateToken generate hexadecimal token +func GenerateToken() (string, error) { + secret, err := generateSecret(constant.TokenLength) + if err != nil { + return "", err + } + + token := fmt.Sprintf("%x", secret) + + return token, nil +} + +// generateArgon2ID Generate an argon2id hash from source string and specified salt +func (config config) generateArgon2ID(source string, salt []byte) []byte { + hash := argon2.IDKey([]byte(source), salt, config.iterations, config.memory, config.threads, config.keyLength) + + return hash +} + +// Password hash a source string with argon2id, return properly encoded hash +func Password(password string) (string, error) { + config := config{ + saltLength: constant.ArgonSaltSize, + memory: constant.ArgonMemory, + threads: constant.ArgonThreads, + keyLength: constant.ArgonKeyLength, + iterations: constant.ArgonIterations, + } + + salt, err := generateSecret(config.saltLength) + if err != nil { + return "", err + } + + hash := config.generateArgon2ID(password, salt) + + base64Hash := base64.RawStdEncoding.EncodeToString(hash) + base64Salt := base64.RawStdEncoding.EncodeToString(salt) + + formatted := fmt.Sprintf("$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s", argon2.Version, config.memory, config.iterations, config.threads, base64Salt, base64Hash) + + return formatted, nil +} + +// VerifyPassword check is source password and stored password is similar, take password and a properly encoded hash. +func VerifyPassword(password string, hash string) (bool, error) { + argon2Hash, config, err := parseHash(hash) + if err != nil { + return false, err + } + + result := config.generateArgon2ID(password, argon2Hash.salt) + + return bytes.Equal(result, argon2Hash.hash), nil +} + +// parseHash parse existing encoded argon2id string +func parseHash(source string) (argon2idHash, config, error) { + separateItem := strings.Split(source, "$") + if len(separateItem) != 6 { + return argon2idHash{}, config{}, &parseError{message: "Hash format is not valid"} + } + + if separateItem[1] != "argon2id" { + return argon2idHash{}, config{}, &parseError{message: "Algorithm is not valid"} + } + + separateParam := strings.Split(separateItem[3], ",") + if len(separateParam) != 3 { + return argon2idHash{}, config{}, &parseError{message: "Hash config is not valid"} + } + + salt, err := base64.RawStdEncoding.Strict().DecodeString(separateItem[4]) + if err != nil { + return argon2idHash{}, config{}, err + } + + var hash []byte + hash, err = base64.RawStdEncoding.Strict().DecodeString(separateItem[5]) + if err != nil { + return argon2idHash{}, config{}, err + } + + saltLength := uint8(len(salt)) + keyLength := uint32(len(hash)) + + var memory int + memory, err = strconv.Atoi(strings.Replace(separateParam[0], "m=", "", -1)) + if err != nil { + return argon2idHash{}, config{}, err + } + + var iterations int + iterations, err = strconv.Atoi(strings.Replace(separateParam[1], "t=", "", -1)) + if err != nil { + return argon2idHash{}, config{}, err + } + + var threads int + threads, err = strconv.Atoi(strings.Replace(separateParam[2], "p=", "", -1)) + if err != nil { + return argon2idHash{}, config{}, err + } + + return argon2idHash{salt: salt, hash: hash}, config{saltLength: saltLength, memory: uint32(memory), threads: uint8(threads), iterations: uint32(iterations), keyLength: keyLength}, nil +} diff --git a/internal/secret/error.go b/internal/secret/error.go new file mode 100644 index 0000000..2b82c3a --- /dev/null +++ b/internal/secret/error.go @@ -0,0 +1,9 @@ +package secret + +type parseError struct { + message string +} + +func (m *parseError) Error() string { + return "parseHash: " + m.message +} diff --git a/internal/utils/error.go b/internal/utils/error.go index d946d64..87acf33 100644 --- a/internal/utils/error.go +++ b/internal/utils/error.go @@ -1,17 +1,17 @@ package utils -type ParseIntBeforeSeparatorError struct { - Message string +type parseIntBeforeSeparatorError struct { + message string } -func (m *ParseIntBeforeSeparatorError) Error() string { - return "parseIntBeforeSeparator: " + m.Message +func (m *parseIntBeforeSeparatorError) Error() string { + return "parseIntBeforeSeparator: " + m.message } type ParseExpirationError struct { - Message string + message string } func (m *ParseExpirationError) Error() string { - return "parseIntBeforeSeparator: " + m.Message + return "parseIntBeforeSeparator: " + m.message } diff --git a/internal/utils/utils.go b/internal/utils/utils.go index e3a98b1..fc86af6 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -1,8 +1,6 @@ package utils import ( - "crypto/rand" - "encoding/hex" "log" mathrand "math/rand/v2" "regexp" @@ -21,17 +19,6 @@ func GenerateUrl(length uint8) string { return string(b) } -// GenerateSecret generate random secret (32 bytes hexadecimal) -func GenerateSecret() string { - key := make([]byte, 32) - _, err := rand.Read(key) - if err != nil { - log.Printf("Failed to generate secret") - } - - return hex.EncodeToString(key) -} - // CheckCharRedundant verify is a character is redundant in a string func CheckCharRedundant(source string, char string) bool { // Verify if a char is redundant return strings.Count(source, char) > 1 @@ -39,7 +26,7 @@ func CheckCharRedundant(source string, char string) bool { // Verify if a char i func parseIntBeforeSeparator(source *string, sep string) (int, error) { // return 0 & error if error, only accept positive number if CheckCharRedundant(*source, sep) { - return 0, &ParseIntBeforeSeparatorError{Message: *source + ": cannot parse value as int"} + return 0, &parseIntBeforeSeparatorError{message: *source + ": cannot parse value as int"} } var value int var err error @@ -47,14 +34,14 @@ func parseIntBeforeSeparator(source *string, sep string) (int, error) { // retur value, err = strconv.Atoi(strings.Split(*source, sep)[0]) if err != nil { log.Println(err) - return 0, &ParseIntBeforeSeparatorError{Message: *source + ": cannot parse value as int"} + return 0, &parseIntBeforeSeparatorError{message: *source + ": cannot parse value as int"} } if value < 0 { // Only positive value is correct - return 0, &ParseIntBeforeSeparatorError{Message: *source + ": format only take positive value"} + return 0, &parseIntBeforeSeparatorError{message: *source + ": format only take positive value"} } if value > 99 { - return 0, &ParseIntBeforeSeparatorError{Message: *source + ": Format only take two number"} + return 0, &parseIntBeforeSeparatorError{message: *source + ": Format only take two number"} } *source = strings.Join(strings.Split(*source, sep)[1:], "") @@ -77,25 +64,25 @@ func ParseExpiration(source string) (int, error) { expiration = tempOutput * 86400 if err != nil { log.Println(err) - return 0, &ParseExpirationError{Message: "Invalid syntax"} + return 0, &ParseExpirationError{message: "Invalid syntax"} } tempOutput, err = parseIntBeforeSeparator(&source, "h") expiration += tempOutput * 3600 if err != nil { log.Println(err) - return 0, &ParseExpirationError{Message: "Invalid syntax"} + return 0, &ParseExpirationError{message: "Invalid syntax"} } tempOutput, err = parseIntBeforeSeparator(&source, "m") expiration += tempOutput * 60 if err != nil { log.Println(err) - return 0, &ParseExpirationError{Message: "Invalid syntax"} + return 0, &ParseExpirationError{message: "Invalid syntax"} } tempOutput, err = parseIntBeforeSeparator(&source, "s") expiration += tempOutput * 1 if err != nil { log.Println(err) - return 0, &ParseExpirationError{Message: "Invalid syntax"} + return 0, &ParseExpirationError{message: "Invalid syntax"} } return expiration, nil diff --git a/internal/web/plak/error.go b/internal/web/plak/error.go index e2241f0..46c1fbf 100644 --- a/internal/web/plak/error.go +++ b/internal/web/plak/error.go @@ -1,10 +1,18 @@ package plak -type DeletePlakError struct { - Name string - Err error +type deletePlakError struct { + name string + err error } -func (m *DeletePlakError) Error() string { - return "Cannot delete: " + m.Name + " : " + m.Err.Error() +func (m *deletePlakError) Error() string { + return "Cannot delete: " + m.name + " : " + m.err.Error() +} + +type createError struct { + message string +} + +func (m *createError) Error() string { + return "create: cannot create plak: " + m.message } diff --git a/internal/web/plak/plak.go b/internal/web/plak/plak.go index b80c137..f65f0f5 100644 --- a/internal/web/plak/plak.go +++ b/internal/web/plak/plak.go @@ -10,6 +10,7 @@ import ( "git.gnous.eu/gnouseu/plakken/internal/constant" "git.gnous.eu/gnouseu/plakken/internal/database" + "git.gnous.eu/gnouseu/plakken/internal/secret" "git.gnous.eu/gnouseu/plakken/internal/utils" "github.com/redis/go-redis/v9" @@ -24,26 +25,48 @@ type WebConfig struct { Templates embed.FS } -// Plak "Object" for plak -type Plak struct { +// plak "Object" for plak +type plak struct { Key string Content string Expiration time.Duration DB *redis.Client } -// Create manage POST request for create Plak -func (config WebConfig) Create(w http.ResponseWriter, r *http.Request) { +// create a plak +func (plak plak) create() (string, error) { + dbConf := database.DBConfig{ + DB: plak.DB, + } + + token, err := secret.GenerateToken() + if err != nil { + return "", err + } + + if dbConf.UrlExist(plak.Key) { + return "", &createError{message: "key already exist"} + } + + var hashedSecret string + hashedSecret, err = secret.Password(token) + if err != nil { + return "", err + } + + dbConf.InsertPaste(plak.Key, plak.Content, hashedSecret, plak.Expiration) + + return token, nil +} + +// PostCreate manage POST request for create plak +func (config WebConfig) PostCreate(w http.ResponseWriter, r *http.Request) { content := r.FormValue("content") if content == "" { w.WriteHeader(http.StatusBadRequest) return } - dbConf := database.DBConfig{ - DB: config.DB, - } - filename := r.FormValue("filename") var key string if len(filename) == 0 { @@ -53,46 +76,67 @@ func (config WebConfig) Create(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusBadRequest) return } - if dbConf.UrlExist(filename) { - w.WriteHeader(http.StatusBadRequest) - return - } key = filename } - secret := utils.GenerateSecret() - rawExpiration := r.FormValue("exp") + plak := plak{ + Key: key, + Content: content, + DB: config.DB, + } + rawExpiration := r.FormValue("exp") expiration, err := utils.ParseExpiration(rawExpiration) if err != nil { - w.WriteHeader(http.StatusBadRequest) + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) return - } else if expiration == 0 { - dbConf.InsertPaste(key, content, secret, -1) + } + + if expiration == 0 { + plak.Expiration = -1 } else { - dbConf.InsertPaste(key, content, secret, time.Duration(expiration*int(time.Second))) + plak.Expiration = time.Duration(expiration * int(time.Second)) + } + + _, err = plak.create() + if err != nil { + log.Println(err) + w.WriteHeader(http.StatusInternalServerError) + return } http.Redirect(w, r, "/"+key, http.StatusSeeOther) } -// CurlCreate Create plak with minimum param, ideal for curl. Force 7 day expiration +// CurlCreate PostCreate plak with minimum param, ideal for curl. Force 7 day expiration func (config WebConfig) CurlCreate(w http.ResponseWriter, r *http.Request) { if r.ContentLength == 0 { w.WriteHeader(http.StatusBadRequest) return } + content, _ := io.ReadAll(r.Body) err := r.Body.Close() if err != nil { log.Println(err) } + key := utils.GenerateUrl(config.UrlLength) - secret := utils.GenerateSecret() - dbConf := database.DBConfig{ - DB: config.DB, + + plak := plak{ + Key: key, + Content: string(content), + Expiration: constant.ExpirationCurlCreate, + DB: config.DB, + } + + var token string + token, err = plak.create() + if err != nil { + w.WriteHeader(http.StatusBadRequest) + return } - dbConf.InsertPaste(key, string(content), secret, constant.ExpirationCurlCreate) var baseURL string if r.TLS == nil { @@ -101,7 +145,7 @@ func (config WebConfig) CurlCreate(w http.ResponseWriter, r *http.Request) { baseURL = "https://" + r.Host + "/" + key } - message := baseURL + "\n" + "Delete with : 'curl -X DELETE " + baseURL + "?secret\\=" + secret + "'" + "\n" + message := baseURL + "\n" + "Delete with : 'curl -X DELETE " + baseURL + "?secret\\=" + token + "'" + "\n" w.Header().Set("Content-Type", "text/plain; charset=utf-8") _, err = io.WriteString(w, message) @@ -115,18 +159,18 @@ func (config WebConfig) View(w http.ResponseWriter, r *http.Request) { dbConf := database.DBConfig{ DB: config.DB, } - var plak Plak + var currentPlak plak key := r.PathValue("key") if dbConf.UrlExist(key) { - plak = Plak{ + currentPlak = plak{ Key: key, DB: config.DB, } - plak = plak.GetContent() + currentPlak = currentPlak.getContent() if r.PathValue("settings") == "raw" { w.Header().Set("Content-Type", "text/plain; charset=utf-8") - _, err := io.WriteString(w, plak.Content) + _, err := io.WriteString(w, currentPlak.Content) if err != nil { log.Println(err) } @@ -135,10 +179,13 @@ func (config WebConfig) View(w http.ResponseWriter, r *http.Request) { if err != nil { w.WriteHeader(http.StatusInternalServerError) log.Println(err) + return } - err = t.Execute(w, plak) + err = t.Execute(w, currentPlak) if err != nil { + w.WriteHeader(http.StatusInternalServerError) log.Println(err) + return } } } else { @@ -146,24 +193,35 @@ func (config WebConfig) View(w http.ResponseWriter, r *http.Request) { } } -// Delete manage plak deletion endpoint -func (config WebConfig) Delete(w http.ResponseWriter, r *http.Request) { +// DeleteRequest manage plak deletion endpoint +func (config WebConfig) DeleteRequest(w http.ResponseWriter, r *http.Request) { dbConf := database.DBConfig{ DB: config.DB, } key := r.PathValue("key") + var valid bool if dbConf.UrlExist(key) { - secret := r.URL.Query().Get("secret") - if dbConf.VerifySecret(key, secret) { - plak := Plak{ + var err error + token := r.URL.Query().Get("secret") + + valid, err = dbConf.VerifySecret(key, token) + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + log.Println(err) + return + } + + if valid { + plak := plak{ Key: key, DB: config.DB, } - err := plak.deletePlak() + err := plak.delete() if err != nil { log.Println(err) } + w.WriteHeader(http.StatusNoContent) return } else { @@ -171,22 +229,23 @@ func (config WebConfig) Delete(w http.ResponseWriter, r *http.Request) { return } } + w.WriteHeader(http.StatusNotFound) } -// deletePlak Delete plak from database -func (plak Plak) deletePlak() error { +// delete DeleteRequest plak from database +func (plak plak) delete() error { err := plak.DB.Del(ctx, plak.Key).Err() if err != nil { log.Println(err) - return &DeletePlakError{Name: plak.Key, Err: err} + return &deletePlakError{name: plak.Key, err: err} } return nil } -// GetContent get plak content -func (plak Plak) GetContent() Plak { +// getContent get plak content +func (plak plak) getContent() plak { plak.Content = plak.DB.HGet(ctx, plak.Key, "content").Val() return plak } diff --git a/test/secret/secret_test.go b/test/secret/secret_test.go new file mode 100644 index 0000000..fad6ba6 --- /dev/null +++ b/test/secret/secret_test.go @@ -0,0 +1,47 @@ +package secret_test + +import ( + "fmt" + "regexp" + "testing" + + "git.gnous.eu/gnouseu/plakken/internal/constant" + "git.gnous.eu/gnouseu/plakken/internal/secret" + "golang.org/x/crypto/argon2" +) + +func TestPasswordFormat(t *testing.T) { + regex := fmt.Sprintf("\\$argon2id\\$v=%d\\$m=%d,t=%d,p=%d\\$[A-Za-z0-9+/]*\\$[A-Za-z0-9+/]*$", argon2.Version, constant.ArgonMemory, constant.ArgonIterations, constant.ArgonThreads) + + got, err := secret.Password("Password!") + if err != nil { + t.Fatal(err) + } + + result, _ := regexp.MatchString(regex, got) + if !result { + t.Fatal("Error in Password, format is not valid "+": ", got) + } +} + +func TestVerifyPassword(t *testing.T) { + result, err := secret.VerifyPassword("Password!", "$argon2id$v=19$m=65536,t=2,p=4$A+t5YGpyy1BHCbvk/LP1xQ$eNuUj6B2ZqXlGi6KEqep39a7N4nysUIojuPXye+Ypp0") + if err != nil { + t.Fatal(err) + } + + if !result { + t.Fatal("Error in VerifyPassword, got:", result, "want: ", true) + } +} + +func TestVerifyPasswordInvalid(t *testing.T) { + result, err := secret.VerifyPassword("notsamepassword", "$argon2id$v=19$m=65536,t=2,p=4$A+t5YGpyy1BHCbvk/LP1xQ$eNuUj6B2ZqXlGi6KEqep39a7N4nysUIojuPXye+Ypp0") + if err != nil { + t.Fatal(err) + } + + if result { + t.Fatal("Error in VerifyPassword, got:", result, "want: ", false) + } +} -- 2.43.4 From e67727b95f5a18ac9d7de7047574f7cda39a75cc Mon Sep 17 00:00:00 2001 From: Ada Date: Tue, 5 Mar 2024 19:34:48 +0100 Subject: [PATCH 23/24] :arrow_up: bump x/crypto and x/sys dependancies --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index a60a2d0..8c8a25f 100644 --- a/go.mod +++ b/go.mod @@ -4,11 +4,11 @@ go 1.22 require ( github.com/redis/go-redis/v9 v9.5.1 - golang.org/x/crypto v0.20.0 + golang.org/x/crypto v0.21.0 ) require ( github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect ) diff --git a/go.sum b/go.sum index 899f6d8..3f64f52 100644 --- a/go.sum +++ b/go.sum @@ -8,7 +8,7 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/redis/go-redis/v9 v9.5.1 h1:H1X4D3yHPaYrkL5X06Wh6xNVM/pX0Ft4RV0vMGvLBh8= github.com/redis/go-redis/v9 v9.5.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= -golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= -golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -- 2.43.4 From 511865496ed470b4917d0d4bbfa3a31f11443573 Mon Sep 17 00:00:00 2001 From: Ada Date: Sun, 24 Mar 2024 20:56:03 +0100 Subject: [PATCH 24/24] :recycle: Restructure test Specific test module require to unnecessary export function --- internal/secret/crypto_test.go | 81 ++++++++++++++++++++++++++ internal/utils/utils.go | 6 +- {test => internal}/utils/utils_test.go | 40 ++++++------- test/secret/secret_test.go | 47 --------------- 4 files changed, 103 insertions(+), 71 deletions(-) create mode 100644 internal/secret/crypto_test.go rename {test => internal}/utils/utils_test.go (73%) delete mode 100644 test/secret/secret_test.go diff --git a/internal/secret/crypto_test.go b/internal/secret/crypto_test.go new file mode 100644 index 0000000..f983646 --- /dev/null +++ b/internal/secret/crypto_test.go @@ -0,0 +1,81 @@ +package secret + +import ( + "errors" + "fmt" + "regexp" + "testing" + + "git.gnous.eu/gnouseu/plakken/internal/constant" + "golang.org/x/crypto/argon2" +) + +func TestPasswordFormat(t *testing.T) { + regex := fmt.Sprintf("\\$argon2id\\$v=%d\\$m=%d,t=%d,p=%d\\$[A-Za-z0-9+/]*\\$[A-Za-z0-9+/]*$", argon2.Version, constant.ArgonMemory, constant.ArgonIterations, constant.ArgonThreads) + + got, err := Password("Password!") + if err != nil { + t.Fatal(err) + } + + result, _ := regexp.MatchString(regex, got) + if !result { + t.Fatal("Error in Password, format is not valid "+": ", got) + } +} + +func TestVerifyPassword(t *testing.T) { + result, err := VerifyPassword("Password!", "$argon2id$v=19$m=65536,t=2,p=4$A+t5YGpyy1BHCbvk/LP1xQ$eNuUj6B2ZqXlGi6KEqep39a7N4nysUIojuPXye+Ypp0") + if err != nil { + t.Fatal(err) + } + + if !result { + t.Fatal("Error in VerifyPassword, got:", result, "want: ", true) + } +} + +func TestVerifyPasswordInvalid(t *testing.T) { + result, err := VerifyPassword("notsamepassword", "$argon2id$v=19$m=65536,t=2,p=4$A+t5YGpyy1BHCbvk/LP1xQ$eNuUj6B2ZqXlGi6KEqep39a7N4nysUIojuPXye+Ypp0") + if err != nil { + t.Fatal(err) + } + + if result { + t.Fatal("Error in VerifyPassword, got:", result, "want: ", false) + } +} + +func TestParseHash(t *testing.T) { + _, config, err := parseHash("$argon2id$v=19$m=65536,t=2,p=4$A+t5YGpyy1BHCbvk/LP1xQ$eNuUj6B2ZqXlGi6KEqep39a7N4nysUIojuPXye+Ypp0") + if err != nil { + t.Fatal(err) + } + if config.saltLength != constant.ArgonSaltSize { + t.Fatal("Error in VerifyPassword: config.saltLength are not correct, go: ", config.saltLength, "want: ", constant.ArgonSaltSize) + } + + if config.keyLength != constant.ArgonKeyLength { + t.Fatal("Error in VerifyPassword: config.keyLength are not correct, go: ", config.saltLength, "want: ", constant.ArgonKeyLength) + } + + if config.threads != constant.ArgonThreads { + t.Fatal("Error in VerifyPassword: config.threads are not correct, go: ", config.threads, "want: ", constant.ArgonThreads) + } + + if config.memory != constant.ArgonMemory { + t.Fatal("Error in VerifyPassword: config.memory are not correct, go: ", config.memory, "want: ", constant.ArgonMemory) + } + + if config.iterations != constant.ArgonIterations { + t.Fatal("Error in VerifyPassword: config.iterations are not correct, go: ", config.iterations, "want: ", constant.ArgonIterations) + } +} + +func TestParseBadHashAlgo(t *testing.T) { + _, _, err := parseHash("$notvalid$v=19$m=65536,t=2,p=4$A+t5YGpyy1BHCbvk/LP1xQ$eNuUj6B2ZqXlGi6KEqep39a7N4nysUIojuPXye+Ypp0") + want := &parseError{message: "Algorithm is not valid"} + if !errors.As(err, &want) { + t.Fatal("Error in parseHash, want :", want, "got: ", err) + } +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index fc86af6..506f07f 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -19,13 +19,13 @@ func GenerateUrl(length uint8) string { return string(b) } -// CheckCharRedundant verify is a character is redundant in a string -func CheckCharRedundant(source string, char string) bool { // Verify if a char is redundant +// checkCharRedundant verify is a character is redundant in a string +func checkCharRedundant(source string, char string) bool { // Verify if a char is redundant return strings.Count(source, char) > 1 } func parseIntBeforeSeparator(source *string, sep string) (int, error) { // return 0 & error if error, only accept positive number - if CheckCharRedundant(*source, sep) { + if checkCharRedundant(*source, sep) { return 0, &parseIntBeforeSeparatorError{message: *source + ": cannot parse value as int"} } var value int diff --git a/test/utils/utils_test.go b/internal/utils/utils_test.go similarity index 73% rename from test/utils/utils_test.go rename to internal/utils/utils_test.go index 3525443..f5ae6fe 100644 --- a/test/utils/utils_test.go +++ b/internal/utils/utils_test.go @@ -1,30 +1,28 @@ -package utils_test +package utils import ( "errors" "testing" - - "git.gnous.eu/gnouseu/plakken/internal/utils" ) -func TestCheckCharNotRedundantTrue(t *testing.T) { // Test CheckCharRedundant with redundant char +func TestCheckCharNotRedundantTrue(t *testing.T) { // Test checkCharRedundant with redundant char want := true - got := utils.CheckCharRedundant("2d1h3md4h7s", "h") + got := checkCharRedundant("2d1h3md4h7s", "h") if got != want { t.Fatal("Error in parseExpirationFull, want : ", want, "got : ", got) } } -func TestCheckCharNotRedundantFalse(t *testing.T) { // Test CheckCharRedundant with not redundant char +func TestCheckCharNotRedundantFalse(t *testing.T) { // Test checkCharRedundant with not redundant char want := false - got := utils.CheckCharRedundant("2d1h3m47s", "h") + got := checkCharRedundant("2d1h3m47s", "h") if got != want { t.Fatal("Error in parseExpirationFull, want : ", want, "got : ", got) } } func TestParseExpirationFull(t *testing.T) { // test parseExpirationFull with all valid separator - result, _ := utils.ParseExpiration("2d1h3m47s") + result, _ := ParseExpiration("2d1h3m47s") correctValue := 176627 if result != correctValue { t.Fatal("Error in parseExpirationFull, want : ", correctValue, "got : ", result) @@ -32,7 +30,7 @@ func TestParseExpirationFull(t *testing.T) { // test parseExpirationFull with al } func TestParseExpirationMissing(t *testing.T) { // test parseExpirationFull with all valid separator - result, _ := utils.ParseExpiration("1h47s") + result, _ := ParseExpiration("1h47s") correctValue := 3647 if result != correctValue { t.Fatal("Error in ParseExpirationFull, want : ", correctValue, "got : ", result) @@ -40,7 +38,7 @@ func TestParseExpirationMissing(t *testing.T) { // test parseExpirationFull with } func TestParseExpirationWithCaps(t *testing.T) { // test parseExpirationFull with all valid separator - result, _ := utils.ParseExpiration("2D1h3M47s") + result, _ := ParseExpiration("2D1h3M47s") correctValue := 176627 if result != correctValue { t.Fatal("Error in parseExpirationFull, want : ", correctValue, "got : ", result) @@ -48,7 +46,7 @@ func TestParseExpirationWithCaps(t *testing.T) { // test parseExpirationFull wit } func TestParseExpirationNull(t *testing.T) { // test ParseExpirationFull with all valid separator - result, _ := utils.ParseExpiration("0") + result, _ := ParseExpiration("0") correctValue := 0 if result != correctValue { t.Fatal("Error in ParseExpirationFull, want: ", correctValue, "got: ", result) @@ -56,16 +54,16 @@ func TestParseExpirationNull(t *testing.T) { // test ParseExpirationFull with al } func TestParseExpirationNegative(t *testing.T) { // test ParseExpirationFull with all valid separator - _, got := utils.ParseExpiration("-42h1m4s") - want := &utils.ParseExpirationError{} + _, got := ParseExpiration("-42h1m4s") + want := &ParseExpirationError{} if !errors.As(got, &want) { t.Fatal("Error in ParseExpirationFull, want : ", want, "got : ", got) } } func TestParseExpirationInvalid(t *testing.T) { // test ParseExpirationFull with all valid separator - _, got := utils.ParseExpiration("8h42h1m1d4s") - want := &utils.ParseExpirationError{} + _, got := ParseExpiration("8h42h1m1d4s") + want := &ParseExpirationError{} if !errors.As(got, &want) { t.Fatal("Error in ParseExpirationFull, want : ", want, "got : ", got) } @@ -73,23 +71,23 @@ func TestParseExpirationInvalid(t *testing.T) { // test ParseExpirationFull with } func TestParseExpirationInvalidRedundant(t *testing.T) { // test ParseExpirationFull with all valid separator - _, got := utils.ParseExpiration("8h42h1m1h4s") - want := &utils.ParseExpirationError{} + _, got := ParseExpiration("8h42h1m1h4s") + want := &ParseExpirationError{} if !errors.As(got, &want) { t.Fatal("Error in ParseExpirationFull, want : ", want, "got : ", got) } } func TestParseExpirationInvalidTooHigh(t *testing.T) { // test ParseExpirationFull with all valid separator - _, got := utils.ParseExpiration("2d1h3m130s") - want := &utils.ParseExpirationError{} + _, got := ParseExpiration("2d1h3m130s") + want := &ParseExpirationError{} if !errors.As(got, &want) { t.Fatal("Error in ParseExpirationFull, want : ", want, "got : ", got) } } func TestValidKey(t *testing.T) { // test ValidKey with a valid key - got := utils.ValidKey("ab_a-C42") + got := ValidKey("ab_a-C42") want := true if got != want { @@ -98,7 +96,7 @@ func TestValidKey(t *testing.T) { // test ValidKey with a valid key } func TestInValidKey(t *testing.T) { // test ValidKey with invalid key - got := utils.ValidKey("ab_?a-C42") + got := ValidKey("ab_?a-C42") want := false if got != want { diff --git a/test/secret/secret_test.go b/test/secret/secret_test.go deleted file mode 100644 index fad6ba6..0000000 --- a/test/secret/secret_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package secret_test - -import ( - "fmt" - "regexp" - "testing" - - "git.gnous.eu/gnouseu/plakken/internal/constant" - "git.gnous.eu/gnouseu/plakken/internal/secret" - "golang.org/x/crypto/argon2" -) - -func TestPasswordFormat(t *testing.T) { - regex := fmt.Sprintf("\\$argon2id\\$v=%d\\$m=%d,t=%d,p=%d\\$[A-Za-z0-9+/]*\\$[A-Za-z0-9+/]*$", argon2.Version, constant.ArgonMemory, constant.ArgonIterations, constant.ArgonThreads) - - got, err := secret.Password("Password!") - if err != nil { - t.Fatal(err) - } - - result, _ := regexp.MatchString(regex, got) - if !result { - t.Fatal("Error in Password, format is not valid "+": ", got) - } -} - -func TestVerifyPassword(t *testing.T) { - result, err := secret.VerifyPassword("Password!", "$argon2id$v=19$m=65536,t=2,p=4$A+t5YGpyy1BHCbvk/LP1xQ$eNuUj6B2ZqXlGi6KEqep39a7N4nysUIojuPXye+Ypp0") - if err != nil { - t.Fatal(err) - } - - if !result { - t.Fatal("Error in VerifyPassword, got:", result, "want: ", true) - } -} - -func TestVerifyPasswordInvalid(t *testing.T) { - result, err := secret.VerifyPassword("notsamepassword", "$argon2id$v=19$m=65536,t=2,p=4$A+t5YGpyy1BHCbvk/LP1xQ$eNuUj6B2ZqXlGi6KEqep39a7N4nysUIojuPXye+Ypp0") - if err != nil { - t.Fatal(err) - } - - if result { - t.Fatal("Error in VerifyPassword, got:", result, "want: ", false) - } -} -- 2.43.4