diff --git a/CHANGELOG.md b/CHANGELOG.md index 166e21d..2f156e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,27 +5,40 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.1] - 2022-07-18 + +### Added + +- Max file size on the client now. +- Loading information. + +### Changed + +- Changed encoding from hex to base64. +- Chinese language code. +- Notable speed improvements for big files. + ## [2.0.0] - 2022-07-16 ### Added -- Theming for logo and description text +- Theming for logo and description text. ### Changed -- Moved to redis -- New html sanitizing library +- Moved to redis. +- New html sanitizing library. ## [2.0.0-rc.0] - 2022-07-15 ### Added -- Theming for logo and description text +- Theming for logo and description text. ### Changed -- Moved to redis -- New html sanitizing library +- Moved to redis. +- New html sanitizing library. ## [1.5.3] - 2022-06-07 diff --git a/Dockerfile b/Dockerfile index 34af967..52d503d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ WORKDIR /tmp RUN npm install -g pnpm@7 COPY ./frontend ./ RUN pnpm install +RUN pnpm exec svelte-kit sync RUN pnpm run build diff --git a/README.md b/README.md index 49fc2d9..6771579 100644 --- a/README.md +++ b/README.md @@ -50,15 +50,15 @@ of the notes even if it tried to. ## Environment Variables -| Variable | Default | Description | -| ---------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------- | -| `REDIS` | `redis://redis/` | Redis URL to connect to. | -| `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/). `512 MiB` is the maximum allowed | -| `MAX_VIEWS` | `100` | Maximal number of views. | -| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. | -| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. | -| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable | -| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo | +| Variable | Default | Description | +| ---------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `REDIS` | `redis://redis/` | Redis URL to connect to. | +| `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/).
`512 MiB` is the maximum allowed.
The frontend will show that number including the ~35% encoding overhead. | +| `MAX_VIEWS` | `100` | Maximal number of views. | +| `MAX_EXPIRATION` | `360` | Maximal expiration in minutes. | +| `ALLOW_ADVANCED` | `true` | Allow custom configuration. If set to `false` all notes will be one view only. | +| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable | +| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo | ## Deployment diff --git a/backend/Cargo.lock b/backend/Cargo.lock index fc50778..0591614 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -424,7 +424,7 @@ dependencies = [ [[package]] name = "cryptgeon" -version = "2.0.0" +version = "2.0.1" dependencies = [ "actix-files", "actix-web", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 73bf04f..551c1ba 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cryptgeon" -version = "2.0.0" +version = "2.0.1" authors = ["cupcakearmy "] edition = "2021" diff --git a/frontend/locales/de.json b/frontend/locales/de.json index 86f6a80..a59f9fd 100644 --- a/frontend/locales/de.json +++ b/frontend/locales/de.json @@ -4,13 +4,17 @@ "file": "Datei", "advanced": "erweitert", "create": "erstellen", - "loading": "Läd...", + "loading": "läd", "mode": "Modus", "views": "{n, plural, =0 {Ansichten} =1 {1 Ansicht} other {# Ansichten}}", "minutes": "{n, plural, =0 {Minuten} =1 {1 Minute} other {# Minuten}}", "max": "max", "share_link": "Link teilen", - "copy_clipboard": "in die Zwischenablage kopieren" + "copy_clipboard": "in die Zwischenablage kopieren", + "encrypting": "verschlüsseln", + "decrypting": "entschlüsselt", + "uploading": "hochladen", + "downloading": "wird heruntergeladen" }, "home": { "intro": "Senden Sie ganz einfach vollständig verschlüsselte, sichere Notizen oder Dateien mit einem Klick. Erstellen Sie einfach eine Notiz und teilen Sie den Link.", @@ -28,7 +32,8 @@ "show": { "errors": { "not_found": "wurde nicht gefunden oder wurde bereits gelöscht.", - "decryption_failed": "falsches Passwort. konnte nicht entziffert werden. wahrscheinlich ein defekter Link. Notiz wurde zerstört." + "decryption_failed": "falsches Passwort. konnte nicht entziffert werden. wahrscheinlich ein defekter Link. Notiz wurde zerstört.", + "unsupported_type": "nicht unterstützter Notiztyp." }, "explanation": "Klicken Sie unten, um die Notiz anzuzeigen und zu löschen, wenn der Zähler sein Limit erreicht hat", "show_note": "Notiz anzeigen", diff --git a/frontend/locales/en.json b/frontend/locales/en.json index ae8490f..534530e 100644 --- a/frontend/locales/en.json +++ b/frontend/locales/en.json @@ -4,13 +4,17 @@ "file": "file", "advanced": "advanced", "create": "create", - "loading": "loading...", + "loading": "loading", "mode": "mode", "views": "{n, plural, =0 {views} =1 {1 view} other {# views}}", "minutes": "{n, plural, =0 {minutes} =1 {1 minute} other {# minutes}}", "max": "max", "share_link": "share link", - "copy_clipboard": "copy to clipboard" + "copy_clipboard": "copy to clipboard", + "encrypting": "encrypting", + "decrypting": "decrypting", + "uploading": "uploading", + "downloading": "downloading" }, "home": { "intro": "Easily send fully encrypted, secure notes or files with one click. Just create a note and share the link.", @@ -28,7 +32,8 @@ "show": { "errors": { "not_found": "note was not found or was already deleted.", - "decryption_failed": "wrong password. could not decipher. probably a broken link. note was destroyed." + "decryption_failed": "wrong password. could not decipher. probably a broken link. note was destroyed.", + "unsupported_type": "unsupported note type." }, "explanation": "click below to show and delete the note if the counter has reached it's limit", "show_note": "show note", diff --git a/frontend/locales/es.json b/frontend/locales/es.json index c71daad..b354ce0 100644 --- a/frontend/locales/es.json +++ b/frontend/locales/es.json @@ -4,13 +4,17 @@ "file": "archivo", "advanced": "avanzado", "create": "crear", - "loading": "cargando...", + "loading": "cargando", "mode": "modo", "views": "{n, plural, =0 {vistas} =1 {1 vista} other {# vistas}}", "minutes": "{n, plural, =0 {minutos} =1 {1 minuto} other {# minutos}}", "max": "max", "share_link": "compartir enlace", - "copy_clipboard": "copiar al portapapeles" + "copy_clipboard": "copiar al portapapeles", + "encrypting": "encriptando", + "decrypting": "descifrando", + "uploading": "cargando", + "downloading": "descargando" }, "home": { "intro": "Envía fácilmente notas o archivos totalmente encriptados y seguros con un solo clic. Solo tienes que crear una nota y compartir el enlace.", @@ -28,7 +32,8 @@ "show": { "errors": { "not_found": "la nota no se encontró o ya fue borrada.", - "decryption_failed": "contraseña incorrecta. no se pudo descifrar. probablemente un enlace roto. la nota fue destruida." + "decryption_failed": "contraseña incorrecta. no se pudo descifrar. probablemente un enlace roto. la nota fue destruida.", + "unsupported_type": "tipo de nota no compatible." }, "explanation": "pulse abajo para mostrar y borrar la nota si el contador ha llegado a su límite", "show_note": "mostrar nota", diff --git a/frontend/locales/fr.json b/frontend/locales/fr.json index 5abeae1..770b4c5 100644 --- a/frontend/locales/fr.json +++ b/frontend/locales/fr.json @@ -4,13 +4,17 @@ "file": "fichier", "advanced": "avancé", "create": "créer", - "loading": "chargement...", + "loading": "chargement", "mode": "mode", "views": "{n, plural, =0 {vues} =1 {1 vue} other {# vues}}", "minutes": "{n, plural, =0 {minutes} =1 {1 minute} other {# minutes}}", "max": "max", "share_link": "partager le lien", - "copy_clipboard": "copier dans le presse-papiers" + "copy_clipboard": "copier dans le presse-papiers", + "encrypting": "cryptage", + "decrypting": "déchiffrer", + "uploading": "téléchargement", + "downloading": "téléchargement" }, "home": { "intro": "Envoyez facilement des notes ou des fichiers entièrement cryptés et sécurisés en un seul clic. Il suffit de créer une note et de partager le lien.", @@ -28,7 +32,8 @@ "show": { "errors": { "not_found": "La note n'a pas été trouvée ou a déjà été supprimée.", - "decryption_failed": "mauvais mot de passe. impossible à déchiffrer. probablement un lien brisé. la note a été détruite." + "decryption_failed": "mauvais mot de passe. impossible à déchiffrer. probablement un lien brisé. la note a été détruite.", + "unsupported_type": "type de note non supporté." }, "explanation": "Cliquez ci-dessous pour afficher et supprimer la note si le compteur a atteint sa limite.", "show_note": "note de présentation", diff --git a/frontend/locales/it.json b/frontend/locales/it.json index 08349e2..c87956c 100644 --- a/frontend/locales/it.json +++ b/frontend/locales/it.json @@ -4,13 +4,17 @@ "file": "file", "advanced": "avanzato", "create": "crea", - "loading": "carica...", + "loading": "carica", "mode": "modalita", "views": "{n, plural, =0 {viste} =1 {1 vista} other {# viste}}", "minutes": "{n, plural, =0 {minuti} =1 {1 minuto} other {# minuti}}", "max": "max", "share_link": "condividi link", - "copy_clipboard": "copia negli appunti" + "copy_clipboard": "copia negli appunti", + "encrypting": "criptando", + "decrypting": "decifrando", + "uploading": "caricamento", + "downloading": "scaricando" }, "home": { "intro": "Invia facilmente note o file completamente criptati e sicuri con un solo clic. Basta creare una nota e condividere il link.", @@ -28,7 +32,8 @@ "show": { "errors": { "not_found": "non è stata trovata o è stata già cancellata.", - "decryption_failed": "password sbagliata. non ha potuto decifrare. probabilmente un link rotto. la nota è stata distrutta." + "decryption_failed": "password sbagliata. non ha potuto decifrare. probabilmente un link rotto. la nota è stata distrutta.", + "unsupported_type": "tipo di nota non supportato." }, "explanation": "clicca sotto per mostrare e cancellare la nota se il contatore ha raggiunto il suo limite", "show_note": "mostra la nota", diff --git a/frontend/locales/cn.json b/frontend/locales/zh_CN.json similarity index 86% rename from frontend/locales/cn.json rename to frontend/locales/zh_CN.json index 6572145..08e2c8e 100644 --- a/frontend/locales/cn.json +++ b/frontend/locales/zh_CN.json @@ -4,13 +4,17 @@ "file": "上传文件", "advanced": "高级设置", "create": "创建", - "loading": "加载中...", + "loading": "加载中", "mode": "模式", "views": "{n, plural, =0 {可查看次数} =1 {1 次查看} other {# 次查看}}", "minutes": "{n, plural, =0 {有效期(分钟)} =1 {1 分钟} other {# 分钟}}", "max": "最大值", "share_link": "分享链接", - "copy_clipboard": "复制到剪切版" + "copy_clipboard": "复制到剪切版", + "encrypting": "加密", + "decrypting": "解密", + "uploading": "上传", + "downloading": "下载" }, "home": { "intro": "一键轻松发送 完全加密的 密信或者文件。只需创建一个密信然后分享链接。", @@ -28,7 +32,8 @@ "show": { "errors": { "not_found": "该密信无法被找到或者它已经被删除了!", - "decryption_failed": "密钥错误!您可能不小心粘贴了一个不完整的链接或者正在尝试破解该密信!但无论如何,该密信已被销毁!" + "decryption_failed": "密钥错误!您可能不小心粘贴了一个不完整的链接或者正在尝试破解该密信!但无论如何,该密信已被销毁!", + "unsupported_type": "不支持的票据类型。" }, "explanation": "点击下方的按钮可以查看密信,如果它到达了限制将会被删除", "show_note": "查看密信", diff --git a/frontend/package.json b/frontend/package.json index abcfcab..381d2bb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,8 +11,8 @@ "type": "module", "devDependencies": { "@lokalise/node-api": "^7.3.1", - "@sveltejs/adapter-static": "^1.0.0-next.34", - "@sveltejs/kit": "^1.0.0-next.361", + "@sveltejs/adapter-static": "^1.0.0-next.37", + "@sveltejs/kit": "^1.0.0-next.377", "@types/dompurify": "^2.3.3", "@types/file-saver": "^2.0.5", "adm-zip": "^0.5.9", @@ -23,7 +23,7 @@ "svelte-preprocess": "^4.10.7", "tslib": "^2.4.0", "typescript": "^4.7.4", - "vite": "^3.0.0" + "vite": "^3.0.1" }, "dependencies": { "@fontsource/fira-mono": "^4.5.8", diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 785fd9b..0d78269 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -3,8 +3,8 @@ lockfileVersion: 5.4 specifiers: '@fontsource/fira-mono': ^4.5.8 '@lokalise/node-api': ^7.3.1 - '@sveltejs/adapter-static': ^1.0.0-next.34 - '@sveltejs/kit': ^1.0.0-next.361 + '@sveltejs/adapter-static': ^1.0.0-next.37 + '@sveltejs/kit': ^1.0.0-next.377 '@types/dompurify': ^2.3.3 '@types/file-saver': ^2.0.5 adm-zip: ^0.5.9 @@ -19,7 +19,7 @@ specifiers: svelte-preprocess: ^4.10.7 tslib: ^2.4.0 typescript: ^4.7.4 - vite: ^3.0.0 + vite: ^3.0.1 dependencies: '@fontsource/fira-mono': 4.5.8 @@ -30,8 +30,8 @@ dependencies: devDependencies: '@lokalise/node-api': 7.3.1 - '@sveltejs/adapter-static': 1.0.0-next.35 - '@sveltejs/kit': 1.0.0-next.371_svelte@3.49.0+vite@3.0.0 + '@sveltejs/adapter-static': 1.0.0-next.37 + '@sveltejs/kit': 1.0.0-next.377_svelte@3.49.0+vite@3.0.1 '@types/dompurify': 2.3.3 '@types/file-saver': 2.0.5 adm-zip: 0.5.9 @@ -42,7 +42,7 @@ devDependencies: svelte-preprocess: 4.10.7_uslzfc62di2n2otc2tvfklnwji tslib: 2.4.0 typescript: 4.7.4 - vite: 3.0.0 + vite: 3.0.1 packages: @@ -107,7 +107,7 @@ packages: '@babel/compat-data': 7.18.8 '@babel/core': 7.18.6 '@babel/helper-validator-option': 7.18.6 - browserslist: 4.21.1 + browserslist: 4.21.2 semver: 6.3.0 dev: true @@ -357,37 +357,37 @@ packages: engines: {node: '>=10'} dev: true - /@sveltejs/adapter-static/1.0.0-next.35: - resolution: {integrity: sha512-iIg5nCMJF2/s/Y7zmy9pzp+U3YDBL6OQKmwfJm2H3Afde/XlhOuNlSO6K//hxmLmvrd7Oh6Kb0MLhwVKp0cuUA==} + /@sveltejs/adapter-static/1.0.0-next.37: + resolution: {integrity: sha512-BDFkx4CGAd6pG4e3+zYjy/eM9UDbhkRgXqavUzCO5oT8xXao5TeprY1AIbdzjMTmFjsWdeSXE9TbIsT0iikpyQ==} dependencies: tiny-glob: 0.2.9 dev: true - /@sveltejs/kit/1.0.0-next.371_svelte@3.49.0+vite@3.0.0: - resolution: {integrity: sha512-2MXNb0M97lsEbJx167YNb6ofGRNuNEBUJM9ntIce6usWaurh+2mMg185sA4p0Kl7gl9xSM2D9GXGT7mlaEmJGA==} + /@sveltejs/kit/1.0.0-next.377_svelte@3.49.0+vite@3.0.1: + resolution: {integrity: sha512-DH2v2yUBUuDZ7vzjPXUd/yt1AMR3BIkZN0ubLAvS2C+q5Wbvk7ZvAJhfPZ3OYc3ZpQXe4ZGEcptOjvEYvd1lLA==} engines: {node: '>=16.9'} hasBin: true peerDependencies: svelte: ^3.44.0 - vite: ^2.9.10 + vite: ^3.0.0 dependencies: - '@sveltejs/vite-plugin-svelte': 1.0.0-next.49_svelte@3.49.0+vite@3.0.0 + '@sveltejs/vite-plugin-svelte': 1.0.1_svelte@3.49.0+vite@3.0.1 chokidar: 3.5.3 sade: 1.8.1 svelte: 3.49.0 - vite: 3.0.0 + vite: 3.0.1 transitivePeerDependencies: - diff-match-patch - supports-color dev: true - /@sveltejs/vite-plugin-svelte/1.0.0-next.49_svelte@3.49.0+vite@3.0.0: - resolution: {integrity: sha512-AKh0Ka8EDgidnxWUs8Hh2iZLZovkETkefO99XxZ4sW4WGJ7VFeBx5kH/NIIGlaNHLcrIvK3CK0HkZwC3Cici0A==} - engines: {node: ^14.13.1 || >= 16} + /@sveltejs/vite-plugin-svelte/1.0.1_svelte@3.49.0+vite@3.0.1: + resolution: {integrity: sha512-PorCgUounn0VXcpeJu+hOweZODKmGuLHsLomwqSj+p26IwjjGffmYQfVHtiTWq+NqaUuuHWWG7vPge6UFw4Aeg==} + engines: {node: ^14.18.0 || >= 16} peerDependencies: diff-match-patch: ^1.0.5 svelte: ^3.44.0 - vite: ^2.9.0 + vite: ^3.0.0 peerDependenciesMeta: diff-match-patch: optional: true @@ -399,7 +399,7 @@ packages: magic-string: 0.26.2 svelte: 3.49.0 svelte-hmr: 0.14.12_svelte@3.49.0 - vite: 3.0.0 + vite: 3.0.1 transitivePeerDependencies: - supports-color dev: true @@ -416,7 +416,7 @@ packages: dependencies: '@types/http-cache-semantics': 4.0.1 '@types/keyv': 3.1.4 - '@types/node': 18.0.3 + '@types/node': 18.0.6 '@types/responselike': 1.0.0 dev: true @@ -441,11 +441,11 @@ packages: /@types/keyv/3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 18.0.3 + '@types/node': 18.0.6 dev: true - /@types/node/18.0.3: - resolution: {integrity: sha512-HzNRZtp4eepNitP+BD6k2L6DROIDG4Q0fm4x+dwfsr6LGmROENnok75VGw40628xf+iR24WeMFcHuuBDUAzzsQ==} + /@types/node/18.0.6: + resolution: {integrity: sha512-/xUq6H2aQm261exT6iZTMifUySEt4GR5KX8eYyY+C4MSNPqSh9oNIP7tz2GLKTlFaiBbgZNxffoR3CVRG+cljw==} dev: true /@types/pug/2.0.6: @@ -455,13 +455,13 @@ packages: /@types/responselike/1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: - '@types/node': 18.0.3 + '@types/node': 18.0.6 dev: true /@types/sass/1.43.1: resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==} dependencies: - '@types/node': 18.0.3 + '@types/node': 18.0.6 dev: true /@types/trusted-types/2.0.2: @@ -525,15 +525,15 @@ packages: fill-range: 7.0.1 dev: true - /browserslist/4.21.1: - resolution: {integrity: sha512-Nq8MFCSrnJXSc88yliwlzQe3qNe3VntIjhsArW9IJOEPSHNx23FalwApUVbzAWABLhYJJ7y8AynWI/XM8OdfjQ==} + /browserslist/4.21.2: + resolution: {integrity: sha512-MonuOgAtUB46uP5CezYbRaYKBNt2LxP0yX+Pmj4LkcDFGkn9Cbpi83d9sCjwQDErXsIJSzY5oKGDbgOlF/LPAA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001366 - electron-to-chromium: 1.4.188 + caniuse-lite: 1.0.30001367 + electron-to-chromium: 1.4.192 node-releases: 2.0.6 - update-browserslist-db: 1.0.4_browserslist@4.21.1 + update-browserslist-db: 1.0.4_browserslist@4.21.2 dev: true /buffer-crc32/0.2.13: @@ -549,10 +549,10 @@ packages: resolution: {integrity: sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==} engines: {node: '>=8'} dependencies: - clone-response: 1.0.2 + clone-response: 1.0.3 get-stream: 5.2.0 http-cache-semantics: 4.1.0 - keyv: 4.3.2 + keyv: 4.3.3 lowercase-keys: 2.0.0 normalize-url: 6.1.0 responselike: 2.0.0 @@ -563,8 +563,8 @@ packages: engines: {node: '>=6'} dev: true - /caniuse-lite/1.0.30001366: - resolution: {integrity: sha512-yy7XLWCubDobokgzudpkKux8e0UOOnLHE6mlNJBzT3lZJz6s5atSEzjoL+fsCPkI0G8MP5uVdDx1ur/fXEWkZA==} + /caniuse-lite/1.0.30001367: + resolution: {integrity: sha512-XDgbeOHfifWV3GEES2B8rtsrADx4Jf+juKX2SICJcaUhjYBO3bR96kvEIHa15VU6ohtOhBZuPGGYGbXMRn0NCw==} dev: true /chalk/2.4.2: @@ -591,8 +591,8 @@ packages: fsevents: 2.3.2 dev: true - /clone-response/1.0.2: - resolution: {integrity: sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==} + /clone-response/1.0.3: + resolution: {integrity: sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==} dependencies: mimic-response: 1.0.1 dev: true @@ -674,8 +674,8 @@ packages: engines: {node: '>=12'} dev: true - /electron-to-chromium/1.4.188: - resolution: {integrity: sha512-Zpa1+E+BVmD/orkyz1Z2dAT1XNUuVAHB3GrogfyY66dXN0ZWSsygI8+u6QTDai1ZayLcATDJpcv2Z2AZjEcr1A==} + /electron-to-chromium/1.4.192: + resolution: {integrity: sha512-8nCXyIQY9An88NXAp+PuPy5h3/w5ZY7Iu2lag65Q0XREprcat5F8gKhoHsBUnQcFuCRnmevpR8yEBYRU3d2HDw==} dev: true /end-of-stream/1.4.4: @@ -1118,8 +1118,8 @@ packages: hasBin: true dev: true - /keyv/4.3.2: - resolution: {integrity: sha512-kn8WmodVBe12lmHpA6W8OY7SNh6wVR+Z+wZESF4iF5FCazaVXGWOtnbnvX0tMQ1bO+/TmOD9LziuYMvrIIs0xw==} + /keyv/4.3.3: + resolution: {integrity: sha512-AcysI17RvakTh8ir03+a3zJr5r0ovnAH/XTXei/4HIv3bL2K/jzvgivLK9UuI/JbU1aJjM3NSAnVvVVd3n+4DQ==} dependencies: compress-brotli: 1.3.8 json-buffer: 3.0.1 @@ -1344,8 +1344,8 @@ packages: glob: 7.2.3 dev: true - /rollup/2.76.0: - resolution: {integrity: sha512-9jwRIEY1jOzKLj3nsY/yot41r19ITdQrhs+q3ggNWhr9TQgduHqANvPpS32RNpzGklJu3G1AJfvlZLi/6wFgWA==} + /rollup/2.77.0: + resolution: {integrity: sha512-vL8xjY4yOQEw79DvyXLijhnhh+R/O9zpF/LEgkCebZFtb6ELeN9H3/2T0r8+mp+fFTBHZ5qGpOpW2ela2zRt3g==} engines: {node: '>=10.0.0'} hasBin: true optionalDependencies: @@ -1566,20 +1566,20 @@ packages: hasBin: true dev: true - /update-browserslist-db/1.0.4_browserslist@4.21.1: + /update-browserslist-db/1.0.4_browserslist@4.21.2: resolution: {integrity: sha512-jnmO2BEGUjsMOe/Fg9u0oczOe/ppIDZPebzccl1yDWGLFP16Pa1/RM5wEoKYPG2zstNcDuAStejyxsOuKINdGA==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' dependencies: - browserslist: 4.21.1 + browserslist: 4.21.2 escalade: 3.1.1 picocolors: 1.0.0 dev: true - /vite/3.0.0: - resolution: {integrity: sha512-M7phQhY3+fRZa0H+1WzI6N+/onruwPTBTMvaj7TzgZ0v2TE+N2sdLKxJOfOv9CckDWt5C4HmyQP81xB4dwRKzA==} - engines: {node: '>=14.18.0'} + /vite/3.0.1: + resolution: {integrity: sha512-nefKSglkoEsDpYUkBuT2++L04ktcP8fz8dxLtmZdDdMyhubFSOLFw6BTh/46Fc6tIX/cibs/NVYWNrsqn0k6pQ==} + engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: less: '*' @@ -1599,7 +1599,7 @@ packages: esbuild: 0.14.49 postcss: 8.4.14 resolve: 1.22.1 - rollup: 2.76.0 + rollup: 2.77.0 optionalDependencies: fsevents: 2.3.2 dev: true diff --git a/frontend/src/lib/adapters.ts b/frontend/src/lib/adapters.ts new file mode 100644 index 0000000..5d2c91e --- /dev/null +++ b/frontend/src/lib/adapters.ts @@ -0,0 +1,61 @@ +import type { EncryptedFileDTO, FileDTO } from './api' +import { Crypto } from './crypto' + +abstract class CryptAdapter { + abstract encrypt(plaintext: T, key: CryptoKey): Promise + abstract decrypt(ciphertext: string, key: CryptoKey): Promise +} + +class CryptTextAdapter implements CryptAdapter { + async encrypt(plaintext: string, key: CryptoKey) { + return await Crypto.encrypt(new TextEncoder().encode(plaintext), key) + } + async decrypt(ciphertext: string, key: CryptoKey) { + const plaintext = await Crypto.decrypt(ciphertext, key) + return new TextDecoder().decode(plaintext) + } +} + +class CryptBlobAdapter implements CryptAdapter { + async encrypt(plaintext: Blob, key: CryptoKey) { + return await Crypto.encrypt(await plaintext.arrayBuffer(), key) + } + + async decrypt(ciphertext: string, key: CryptoKey) { + const plaintext = await Crypto.decrypt(ciphertext, key) + return new Blob([plaintext], { type: 'application/octet-stream' }) + } +} + +class CryptFilesAdapter implements CryptAdapter { + async encrypt(plaintext: FileDTO[], key: CryptoKey) { + const adapter = new CryptBlobAdapter() + const data: Promise[] = plaintext.map(async (file) => ({ + name: file.name, + size: file.size, + type: file.type, + contents: await adapter.encrypt(file.contents, key), + })) + return JSON.stringify(await Promise.all(data)) + } + + async decrypt(ciphertext: string, key: CryptoKey) { + const adapter = new CryptBlobAdapter() + const data: EncryptedFileDTO[] = JSON.parse(ciphertext) + const files: FileDTO[] = await Promise.all( + data.map(async (file) => ({ + name: file.name, + size: file.size, + type: file.type, + contents: await adapter.decrypt(file.contents, key), + })) + ) + return files + } +} + +export const Adapters = { + Text: new CryptTextAdapter(), + Blob: new CryptBlobAdapter(), + Files: new CryptFilesAdapter(), +} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 11d3afc..793753d 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -11,6 +11,10 @@ export type NotePublic = Pick export type NoteCreate = Omit & { meta: string } export type FileDTO = Pick & { + contents: Blob +} + +export type EncryptedFileDTO = Omit & { contents: string } diff --git a/frontend/src/lib/crypto.ts b/frontend/src/lib/crypto.ts index c64c879..4a4bab5 100644 --- a/frontend/src/lib/crypto.ts +++ b/frontend/src/lib/crypto.ts @@ -19,53 +19,79 @@ export class Hex { } } -const ALG = 'AES-GCM' +export class ArrayBufferUtils { + static async toString(buffer: ArrayBuffer): Promise { + const reader = new window.FileReader() + reader.readAsDataURL(new Blob([buffer])) + return new Promise((resolve) => { + reader.onloadend = () => resolve(reader.result as string) + }) + } -export function getRandomBytes(size = 16): Uint8Array { - return window.crypto.getRandomValues(new Uint8Array(size)) + static async fromString(s: string): Promise { + return fetch(s) + .then((r) => r.blob()) + .then((b) => b.arrayBuffer()) + } } -export function getKeyFromString(password: string) { - return window.crypto.subtle.importKey( - 'raw', - new TextEncoder().encode(password), - 'PBKDF2', - false, - ['deriveBits', 'deriveKey'] - ) -} +export class Crypto { + private static ALG = 'AES-GCM' + private static DELIMITER = ':::' -export async function getDerivedForKey(key: CryptoKey, salt: ArrayBuffer) { - const iterations = 100_000 - return window.crypto.subtle.deriveKey( - { - name: 'PBKDF2', - salt, - iterations, - hash: 'SHA-512', - }, - key, - { name: ALG, length: 256 }, - true, - ['encrypt', 'decrypt'] - ) -} + public static getRandomBytes(size: number): Uint8Array { + return window.crypto.getRandomValues(new Uint8Array(size)) + } -export async function encrypt(plaintext: string, key: CryptoKey) { - const salt = getRandomBytes(16) - const derived = await getDerivedForKey(key, salt) - const iv = getRandomBytes(16) - const encrypted = await window.crypto.subtle.encrypt( - { name: ALG, iv }, - derived, - new TextEncoder().encode(plaintext) - ) - return [salt, iv, encrypted].map(Hex.encode).join(':') -} + public static getKeyFromString(password: string) { + return window.crypto.subtle.importKey( + 'raw', + new TextEncoder().encode(password), + 'PBKDF2', + false, + ['deriveBits', 'deriveKey'] + ) + } + public static async getDerivedForKey(key: CryptoKey, salt: ArrayBuffer) { + const iterations = 100_000 + return window.crypto.subtle.deriveKey( + { + name: 'PBKDF2', + salt, + iterations, + hash: 'SHA-512', + }, + key, + { name: this.ALG, length: 256 }, + true, + ['encrypt', 'decrypt'] + ) + } -export async function decrypt(ciphertext: string, key: CryptoKey) { - const [salt, iv, encrypted] = ciphertext.split(':').map(Hex.decode) - const derived = await getDerivedForKey(key, salt) - const plaintext = await window.crypto.subtle.decrypt({ name: ALG, iv }, derived, encrypted) - return new TextDecoder().decode(plaintext) + public static async encrypt(plaintext: ArrayBuffer, key: CryptoKey): Promise { + const salt = this.getRandomBytes(16) + const derived = await this.getDerivedForKey(key, salt) + const iv = this.getRandomBytes(16) + const encrypted: ArrayBuffer = await window.crypto.subtle.encrypt( + { name: this.ALG, iv }, + derived, + plaintext + ) + const data = [ + Hex.encode(salt), + Hex.encode(iv), + await ArrayBufferUtils.toString(encrypted), + ].join(this.DELIMITER) + return data + } + + public static async decrypt(ciphertext: string, key: CryptoKey): Promise { + const splitted = ciphertext.split(this.DELIMITER) + const salt = Hex.decode(splitted[0]) + const iv = Hex.decode(splitted[1]) + const encrypted = await ArrayBufferUtils.fromString(splitted[2]) + const derived = await this.getDerivedForKey(key, salt) + const plaintext = await window.crypto.subtle.decrypt({ name: this.ALG, iv }, derived, encrypted) + return plaintext + } } diff --git a/frontend/src/lib/files.ts b/frontend/src/lib/files.ts deleted file mode 100644 index 78a31d2..0000000 --- a/frontend/src/lib/files.ts +++ /dev/null @@ -1,13 +0,0 @@ -export class Files { - static toString(f: File | Blob): Promise { - const reader = new window.FileReader() - reader.readAsDataURL(f) - return new Promise((resolve) => { - reader.onloadend = () => resolve(reader.result as string) - }) - } - - static async fromString(s: string): Promise { - return fetch(s).then((r) => r.blob()) - } -} diff --git a/frontend/src/lib/ui/AdvancedParameters.svelte b/frontend/src/lib/ui/AdvancedParameters.svelte new file mode 100644 index 0000000..4dceecf --- /dev/null +++ b/frontend/src/lib/ui/AdvancedParameters.svelte @@ -0,0 +1,47 @@ + + +
+ + ($status && v < $status?.max_views) || + $t('home.errors.max', { values: { n: $status?.max_views ?? 0 } })} + /> +
+ +
+ + ($status && v < $status?.max_expiration) || + $t('home.errors.max', { values: { n: $status?.max_expiration ?? 0 } })} + /> +
+ + diff --git a/frontend/src/lib/ui/FileUpload.svelte b/frontend/src/lib/ui/FileUpload.svelte index f158349..24687b9 100644 --- a/frontend/src/lib/ui/FileUpload.svelte +++ b/frontend/src/lib/ui/FileUpload.svelte @@ -1,38 +1,32 @@ @@ -57,7 +51,9 @@
{$t('file_upload.no_files_selected')}
- {$t('common.max')}: + + {$t('common.max')}: +
{/if} diff --git a/frontend/src/lib/ui/Loader.svelte b/frontend/src/lib/ui/Loader.svelte new file mode 100644 index 0000000..313485c --- /dev/null +++ b/frontend/src/lib/ui/Loader.svelte @@ -0,0 +1,41 @@ + + + + + + + + + + diff --git a/frontend/src/lib/ui/MaxSize.svelte b/frontend/src/lib/ui/MaxSize.svelte index 8264b67..d107d17 100644 --- a/frontend/src/lib/ui/MaxSize.svelte +++ b/frontend/src/lib/ui/MaxSize.svelte @@ -1,12 +1,17 @@ {#if $status !== null} - {prettyBytes($status.max_size, { binary: true })} + {prettyBytes($status.max_size * overhead, { binary: true })} {:else} {$_('common.loading')} {/if} diff --git a/frontend/src/lib/ui/NoteResult.svelte b/frontend/src/lib/ui/NoteResult.svelte new file mode 100644 index 0000000..b1d1b41 --- /dev/null +++ b/frontend/src/lib/ui/NoteResult.svelte @@ -0,0 +1,36 @@ + + + + + +
+

+ {@html $t('home.new_note_notice')} +

+
+ + + diff --git a/frontend/src/lib/ui/ShowNote.svelte b/frontend/src/lib/ui/ShowNote.svelte index 7d9a3bf..96793b6 100644 --- a/frontend/src/lib/ui/ShowNote.svelte +++ b/frontend/src/lib/ui/ShowNote.svelte @@ -1,20 +1,24 @@ + + {#if result} - -
-

- {@html $t('home.new_note_notice')} -

-
- + {:else}

{@html $status?.theme_text || $t('home.intro')}

-
- {#if file} - (note.contents = f.detail)} /> +
+ {#if isFile} + {:else}