Compare commits

...

189 Commits
v1.3.1 ... main

Author SHA1 Message Date
Niccolo Borgioli 6d2150b0b6
version bump 2024-05-04 16:06:40 +02:00
Nicco 3a68693be1
Merge pull request #128 from cbin/main
change locales
2024-04-26 13:45:17 +02:00
Oleg Salnikov a612eec220
Add files via upload 2024-04-24 18:31:01 +03:00
Nicco 98d3b0d394
Merge pull request #127 from zocimek/main
fix key for home.new_note_notice
2024-04-22 09:38:49 +02:00
Łukasz Pospiech 6aed2e2756
fix key for home.new_note_notice 2024-04-18 12:39:00 +02:00
Nicco 6bb527198a
Merge pull request #123 from zocimek/patch-1
Create polish translation
2024-04-10 16:57:18 +02:00
Łukasz Pospiech 7050389316
Create polish translation 2024-04-05 14:00:58 +02:00
Nicco 0725a0c6f7
Merge pull request #122 from cupcakearmy/dependabot/npm_and_yarn/npm_and_yarn-security-group-e93d6eacd9
Bump the npm_and_yarn group across 1 directory with 1 update
2024-04-05 09:37:44 +02:00
dependabot[bot] c8efcc04fc
Bump the npm_and_yarn group across 1 directory with 1 update
Bumps the npm_and_yarn group with 1 update in the / directory: [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite).


Updates `vite` from 5.1.4 to 5.1.7
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.1.7/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.1.7/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
  dependency-group: npm_and_yarn-security-group
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-03 17:38:45 +00:00
Nicco 19bf155653
Merge pull request #120 from MairusuPawa/patch-1
Update fr.json
2024-03-24 20:57:00 +01:00
MairusuPawa 9a4e84db62
Update fr.json
Original translation confuses "crypter" and "chiffrer".
2024-03-23 16:19:29 +01:00
Niccolo Borgioli 32cd3843a7
version bump 2024-03-21 15:55:21 +01:00
Nicco 9b48d39c82
Merge pull request #119 from compasspathways/main
Misc improvements including `ALLOW_FILES` and `NEW_NOTE_NOTICE`
2024-03-19 16:04:04 +01:00
Tamás Kádár 239e950f8e NEW_NOTE_NOTICE -> THEME_NEW_NOTE_NOTICE 2024-03-19 12:06:46 +00:00
Tamás Kádár b00846ce9d clicking the logo after the creation of the note should do a reset 2024-03-15 14:30:55 +00:00
Tamás Kádár e70f06f99f NEW_NOTE_NOTICE feature flag 2024-03-15 14:26:33 +00:00
Tamás Kádár 4781882c72 fix extra space in /about 2024-03-15 14:19:30 +00:00
Tamás Kádár 549dfb55db update @types/node 2024-03-15 14:16:02 +00:00
Tamás Kádár 2653a4bccf ALLOW_FILES flag 2024-03-15 14:14:17 +00:00
Tamás Kádár 7213e6c690 Minor Dockerfile fixes 2024-03-15 14:11:34 +00:00
Tamás Kádár 8a5f667ff6 Fix file-saver CommonJS error 2024-03-15 13:53:12 +00:00
Niccolo Borgioli fc3938701e
add reset translation 2024-03-04 18:46:00 +01:00
Nicco 23b4f81dac
Merge pull request #118 from codiflow/codiflow-patch-1
Update de.json
2024-03-04 18:42:39 +01:00
Nicco 7c68620d8b
Merge branch 'main' into codiflow-patch-1 2024-03-04 18:32:46 +01:00
Niccolo Borgioli eb76fe085a
rm pkg and update node version 2024-03-04 18:32:03 +01:00
Christian 38540b33f2
Update de.json
Fixed some translations in the German language strings
2024-03-04 17:39:35 +01:00
Niccolo Borgioli 39a9ac0dad
version bump 2024-03-04 15:25:02 +01:00
Nicco ff1b5d500b
Merge pull request #117 from cupcakearmy/also-expose-api-methods-for-programmatic-usage
Also expose api methods for programmatic usage
2024-03-04 15:24:02 +01:00
Niccolo Borgioli 1698abe2eb
pipeline 2024-03-04 15:15:47 +01:00
Niccolo Borgioli 3036927a45
update dependencies 2024-03-03 01:50:52 +01:00
Niccolo Borgioli f9c26ba81c
change entry point for cli test 2024-03-03 01:35:17 +01:00
Niccolo Borgioli 752e68e213
revert to node 18 2024-03-03 01:34:08 +01:00
Niccolo Borgioli 6eb3a59e33
use package.json pnpm version 2024-03-03 01:34:03 +01:00
Niccolo Borgioli 1a2728d21f
also expose internal shared functionality for external usage 2024-03-03 01:22:39 +01:00
Niccolo Borgioli a37a0932e0
translations 2024-02-26 17:19:29 +01:00
Nicco 71a33a7939
Merge pull request #111 from cupcakearmy/dependabot/cargo/packages/backend/zerocopy-0.7.31
Bump zerocopy from 0.7.21 to 0.7.31 in /packages/backend
2024-02-09 14:47:47 +01:00
dependabot[bot] 83033a4b85
Bump zerocopy from 0.7.21 to 0.7.31 in /packages/backend
Bumps [zerocopy](https://github.com/google/zerocopy) from 0.7.21 to 0.7.31.
- [Release notes](https://github.com/google/zerocopy/releases)
- [Changelog](https://github.com/google/zerocopy/blob/main/CHANGELOG.md)
- [Commits](https://github.com/google/zerocopy/compare/v0.7.21...v0.7.31)

---
updated-dependencies:
- dependency-name: zerocopy
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-12-15 03:50:33 +00:00
Nicco 40570bbbaf
Merge pull request #108 from codiflow/patch-1
Updated German language strings
2023-12-13 21:22:44 +01:00
Christian f591e589d0
Updated German language strings 2023-12-04 16:01:09 +01:00
Nicco d1eebb04f3
Merge pull request #107 from pgschk/locale-fixes
Fixes to locales en and de
2023-11-08 13:49:13 +01:00
pgschk 5a76ea7778
Update locale de
Fix some translation errors
2023-11-01 23:29:02 +01:00
pgschk 45a1af7569
Fix locale en
Fix spelling: to -> too, it's -> its
2023-11-01 23:18:58 +01:00
Nicco 9578b2bed2
Merge pull request #106 from cupcakearmy/2.4.0
2.4.0
2023-11-01 15:57:12 +01:00
Niccolo Borgioli f94e4e3858
sparse bundler is enabled by default now 2023-11-01 15:26:55 +01:00
Niccolo Borgioli ffa2c49ea3
don't insert html anymore, use separate link section 2023-11-01 15:26:45 +01:00
Niccolo Borgioli 0d850aadfc
update deps 2023-11-01 15:26:33 +01:00
Niccolo Borgioli a9c646c981
update packages 2023-11-01 15:26:20 +01:00
Niccolo Borgioli a2803a13da
update deps 2023-08-14 10:22:09 +02:00
Niccolo Borgioli deef56776d
maintenance 2023-08-14 10:12:26 +02:00
Nicco b089323990
Merge pull request #97 from MVS/grammar-patch
Update en.json
2023-08-04 09:51:17 +02:00
MVS 6002ede685
Update en.json
Small tweaks to grammar
2023-08-03 12:07:50 -05:00
Nicco 8a444ceb88
Merge pull request #95 from cbin/patch-1
Update ru.json
2023-07-19 16:59:50 +02:00
Oleg Salnikov 1e01ccb65a
Update ru.json
minor changes
2023-07-18 20:59:24 +03:00
Nicco 2dfa9dd248
Merge pull request #94 from Rooyca/main
Spanish translation of about page
2023-07-13 14:37:59 +02:00
Ronald 618e914b55
Add README_ES.md 2023-07-09 14:09:10 -05:00
Ronald 86f596fa4b
Add README_ES.md 2023-07-09 14:07:40 -05:00
Ronald dcb4613f66
Create README_ES.md 2023-07-09 14:05:59 -05:00
Nicco c46f80aaa0
Merge pull request #93 from cupcakearmy/feat/92-health-check
Feat/92 health check
2023-06-23 10:34:18 +02:00
Niccolo Borgioli e2711cc887
add healthcheck endpoint and startup check 2023-06-23 10:17:13 +02:00
Niccolo Borgioli e02224216a
add changelog 2023-06-23 10:16:28 +02:00
Niccolo Borgioli 1b0d5449a0
update postman collection 2023-06-23 10:16:13 +02:00
Niccolo Borgioli 9695d3a63f
version bumps 2023-06-23 10:16:03 +02:00
Niccolo Borgioli 22d4efb03e
add healthcheck examples 2023-06-23 10:15:31 +02:00
Niccolo Borgioli 97741ed73f
add curl for health check 2023-06-23 10:15:14 +02:00
Niccolo Borgioli c9e5de0f37
about page spacing 2023-06-02 23:51:54 +02:00
Nicco dc1c03d912
Merge pull request #90 from cupcakearmy/feature/52-Add-note-id-size-option
feat: add note id size option
2023-05-30 10:31:12 +02:00
Niccolo Borgioli 2a75acae3f
docs 2023-05-30 09:43:41 +02:00
Niccolo Borgioli 815ac4e8ba
changelog 2023-05-30 09:43:31 +02:00
Niccolo Borgioli 7c85c1e621
version bump 2023-05-30 09:43:26 +02:00
Niccolo Borgioli a323d48c41
feat: add note id size option 2023-05-29 16:34:59 +02:00
Niccolo Borgioli 2bff6a37db
add some metadata 2023-05-26 01:10:22 +02:00
Niccolo Borgioli f8223dfc62
enable sparse bundle 2023-05-26 00:21:50 +02:00
Niccolo Borgioli 063d073c27
fix pipeline 2023-05-25 23:54:59 +02:00
Nicco ac32b97383
Merge pull request #89 from cupcakearmy/69/password
69/password
2023-05-25 23:47:08 +02:00
Niccolo Borgioli 9c9c23d958
version bump 2023-05-25 23:29:09 +02:00
Niccolo Borgioli 92893a5b2d
github actions 2023-05-25 23:29:05 +02:00
Niccolo Borgioli ac68f4a540
docs 2023-05-25 19:06:07 +02:00
Niccolo Borgioli 83b2fa5372
version bump 2023-05-25 18:15:31 +02:00
Niccolo Borgioli 3c86f3f3be
update pnpm version 2023-05-25 18:15:18 +02:00
Niccolo Borgioli 80e64ad207
fix types 2023-05-25 18:15:05 +02:00
Niccolo Borgioli a5809c216c
fix scripts 2023-05-25 10:17:08 +02:00
Niccolo Borgioli fb95a68b0d
test files in cli and cross with password 2023-05-25 10:17:01 +02:00
Niccolo Borgioli b43b802221
add --all option, stdin and password option 2023-05-25 10:16:44 +02:00
Niccolo Borgioli 2e89007c83
add test ids 2023-05-25 10:16:12 +02:00
Niccolo Borgioli fdc2722fb9
add password to frontend 2023-05-23 09:39:19 +02:00
Niccolo Borgioli 6da28a701e
translations 2023-05-23 09:39:06 +02:00
Niccolo Borgioli e6d1e0f44a
add password to CLI 2023-05-23 09:39:00 +02:00
Niccolo Borgioli 6000553b95
include meta in info endpoint 2023-05-23 09:38:23 +02:00
Niccolo Borgioli 85204776d7
demo postman collection 2023-05-23 09:38:00 +02:00
Niccolo Borgioli c2bfe9dd0d
add derivation to metadata 2023-05-23 09:37:49 +02:00
Niccolo Borgioli b05841a562
add timeout 2023-05-23 09:37:33 +02:00
Nicco d7e5a34b14
CLI (#84)
* move to packages

* update deps

* update deps

* actions maintenance

* don't use blob

* cli

* fix default import

* use synthetic default imports

* remove comment

* cli packaging

* node 18 guard

* packages

* build system

* testing

* test pipeline

* pipelines

* changelog

* version bump

* update locales

* update deps

* update deps

* update dependecies
2023-05-14 13:52:47 +02:00
Nicco 13dfd933af
Add tutorial (#83) 2023-05-13 08:41:05 +02:00
Oleg Salnikov 74840416f1
locales (#85) 2023-05-13 08:40:50 +02:00
luolongfei 9aaad5b910
update zh.json file. (#79) 2023-03-02 09:55:50 +01:00
dependabot[bot] c246207420
Bump tokio from 1.24.1 to 1.25.0 in /packages/backend (#75)
Bumps [tokio](https://github.com/tokio-rs/tokio) from 1.24.1 to 1.25.0.
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.24.1...tokio-1.25.0)

---
updated-dependencies:
- dependency-name: tokio
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-02-13 19:21:06 +01:00
cupcakearmy 7ee1b8370a
update deps 2023-01-14 18:54:27 +01:00
cupcakearmy e7750699cc
chose: changelog 2023-01-14 18:40:45 +01:00
cupcakearmy e14042ea28
chinese language code 2023-01-14 18:40:16 +01:00
Niccolo Borgioli 6fb7518b6a
moved to occulto 2023-01-13 21:24:27 +01:00
Niccolo Borgioli 436ae2a7e5
move dev ports 2023-01-13 19:36:26 +01:00
cupcakearmy fe5ce580ab
locales 2023-01-04 19:45:25 +01:00
cupcakearmy 0f882da5d1
bump version 2023-01-04 19:45:18 +01:00
cupcakearmy ad6f136dd0
qr code 2023-01-04 19:40:37 +01:00
cupcakearmy da527a0857
2.0.7 2022-12-26 22:54:47 +01:00
cupcakearmy a95931ae77
update deps 2022-12-26 18:00:00 +01:00
cupcakearmy d6c2236673
update deps 2022-12-26 12:59:37 +01:00
cupcakearmy 42a8ab5d0f
test command 2022-11-15 13:15:34 +01:00
cupcakearmy 0934808a59
testing 2022-11-15 13:05:13 +01:00
cupcakearmy 88ea828b66
upload 2022-11-15 12:35:02 +01:00
cupcakearmy 41ed5c0e23
ci 2022-11-15 12:17:52 +01:00
cupcakearmy 0a98481991
use npx 2022-11-15 12:13:03 +01:00
cupcakearmy 5d62c48a35
don't use pnpm 2022-11-14 16:26:51 +01:00
cupcakearmy 0ab39023b0
test 2022-11-14 16:16:50 +01:00
cupcakearmy 7b202962e8
testing 2022-11-14 15:55:49 +01:00
cupcakearmy 7a045b3f34
test on docker image 2022-11-14 15:47:12 +01:00
cupcakearmy cb80c8bfe4
changelog 2022-11-12 14:40:21 +01:00
cupcakearmy 74c3197e47
update dependencies and fix some a11y issues 2022-11-12 14:40:17 +01:00
cupcakearmy 6ae927ce71
update version and dependencies 2022-11-12 13:55:33 +01:00
cupcakearmy 9d13e607f5
#66 set minimum 2022-11-12 13:42:09 +01:00
cupcakearmy 0db3ef4a1f
changelog and only test on x86 2022-11-04 23:34:36 +01:00
cupcakearmy 03e9fb431f
put flows back together 2022-11-04 22:10:19 +01:00
cupcakearmy b84df2866b
build matrix 2022-11-02 13:36:56 +01:00
cupcakearmy 3d4fef7c23
try with matrix build 2022-11-02 13:34:41 +01:00
cupcakearmy 9d787008a4
also build docker when testing 2022-11-02 13:29:19 +01:00
cupcakearmy 687f26bb40
name the workflow 2022-11-02 13:26:57 +01:00
cupcakearmy 371a869800
use nightly cargo with sparse registry 2022-11-02 13:24:30 +01:00
cupcakearmy 321c303a8a
changelog 2022-10-29 19:45:14 +02:00
cupcakearmy 2f176d84e9
wrong docker compose 2022-10-29 19:43:59 +02:00
Nicco 67d4f09bd7
#62 (#63)
* #62 add theme options for title and favicon

* docs

* version bump
2022-10-27 17:26:56 +02:00
Nicco c40f009523
Update README.md 2022-10-24 16:35:54 +02:00
Nicco 026f8c69d7
add size limit to redis 2022-10-24 16:11:50 +02:00
Nicco cacb808117
restructuring (#56)
* restructuring

* pin svelte kit version & parallel execution

* update svelte kit

* correct test result assets

* add timeout

* correct locale path

* simplify crypto

* fix for #58

* add verbosity flag

* disable flaky test
2022-10-07 21:28:25 +02:00
cupcakearmy 2d573edcac
change link 2022-09-12 14:24:05 +02:00
cupcakearmy 4287cd429d
security reporting 2022-09-10 13:13:09 +02:00
cupcakearmy 024dfeeeb7
add url spec 2022-07-26 23:48:53 +02:00
cupcakearmy f24bcba20b
remove ununsed 2022-07-26 15:49:12 +02:00
cupcakearmy 1d95edc455
readme 2022-07-26 15:49:06 +02:00
hash070 ec24ab3edd
Update CN README translate (#47) 2022-07-21 11:32:38 +02:00
Nicco a552e4d766
Toasts (#45)
* locales

* add toasts and update deps

* changelog

* lock file
2022-07-20 10:41:37 +02:00
Nicco c3b1772728
Testing (#44)
* testing

* try playwrigth

* testing

* add pr support

* not on each commit

* add test ids

* make backend more configuratble

* 2.0.2

* spec
2022-07-19 21:55:05 +02:00
Nicco 786878a3e4
Testing (#41)
* testing

* try playwrigth

* testing

* add pr support

* not on each commit
2022-07-19 14:12:51 +02:00
Nicco a5d98b76bd
2.0.1 (#40)
* locale from lokalise

* version bump

* update dependencies

* show size with overhead

* use base64 instead of hex and refactor a bit

* changelog & readme

* size limit

* locale

* add sync for svelte

* refarcor create & add loading animation

* changelog
2022-07-19 10:27:23 +02:00
Nicco 9590c9b567
2 (#38)
* use redis

* update frontend and switch sanitize library

* changelog

* theming

* docker image

* documentation

* changelog

* clear up limit sizes

* version bump

* version bump
2022-07-16 14:16:54 +02:00
hash070 0913a8ad0c
created and made a Chinese translation JSON file at ./frontend/locales/cn.json (#37) 2022-07-12 14:30:47 +02:00
Nicco d13c712e95
Update README.md 2022-07-08 10:45:40 +02:00
Nicco 6230d2dbd0
Merge pull request #36 from Hash070/cn-translate
Completed the Chinese translation of README
2022-07-08 10:44:17 +02:00
hash070 dbfb383c73 Completed the Chinese translation of README 2022-07-08 16:18:12 +08:00
cupcakearmy a257d2cefb
use env var for startup script 2022-06-07 12:41:20 +02:00
cupcakearmy 35ba25ba9e
changelog 2022-06-07 12:29:58 +02:00
cupcakearmy 724dca0e69
update deps 2022-06-07 12:29:54 +02:00
cupcakearmy 9029f72a02
improve docker build and wait for memcached 2022-06-07 12:29:40 +02:00
cupcakearmy 1d55d7f2d2
update deps 2022-06-07 12:29:16 +02:00
cupcakearmy d09bb4e0c6
update deps 2022-06-07 12:29:11 +02:00
cupcakearmy 53c7c9d9e2
1.5.1 2022-05-15 12:13:03 +02:00
cupcakearmy df9c60c29e
1.5.0 2022-05-14 16:57:28 +02:00
Nicco f29b6b23f0
Update README.md 2022-04-12 09:34:38 +02:00
cupcakearmy cc88fa6763
readme 2022-04-12 09:12:40 +02:00
cupcakearmy 19022e7cb5
formatting 2022-03-12 14:07:33 +01:00
cupcakearmy 44f43dbc2c
scratch docs 2022-03-06 12:27:08 +01:00
cupcakearmy 45f6f3af32
readme from scratch 2022-03-06 12:16:19 +01:00
cupcakearmy 9bd544f0d5
add env vars to readme 2022-03-06 12:01:54 +01:00
Nicco a315e58284
Merge pull request #26 from cupcakearmy/1.4.1
fallback route & dep updates
2022-03-05 12:47:55 +01:00
cupcakearmy d576b71bc5
fallback route & dep updates 2022-03-05 12:47:11 +01:00
cupcakearmy e02f7f59c6
changelog 2022-03-02 16:59:25 +01:00
cupcakearmy e8c6467faa
dont' allow empty notes 2022-03-02 16:55:10 +01:00
cupcakearmy 43f67c795d
don't remove already selected files 2022-03-02 16:55:04 +01:00
cupcakearmy 83f0902291
lokalise 2022-03-02 16:32:36 +01:00
cupcakearmy 11a6621bd7
script to download locales 2022-03-02 16:15:25 +01:00
cupcakearmy 36fa451249
enforce limits 2022-03-01 16:16:02 +01:00
cupcakearmy d112eba8fe
add config for max views, expiration and advanced 2022-03-01 15:24:17 +01:00
cupcakearmy ef39f9ec0b
cleanup 2022-03-01 02:04:43 +01:00
cupcakearmy 8517c20e6c
remove unused code 2022-03-01 02:01:57 +01:00
cupcakearmy 728ad56b33
enforce strict typescript 2022-03-01 02:00:01 +01:00
cupcakearmy f185ccee03
add svelte check 2022-03-01 01:52:20 +01:00
cupcakearmy 284bbcbae2
better typings 2022-03-01 01:52:09 +01:00
cupcakearmy 7eba454f1b
visual improvements 2022-03-01 01:52:04 +01:00
cupcakearmy dcd9efaeba
use native icons 2022-03-01 01:51:43 +01:00
cupcakearmy f13bcbaf3f
update robots 2022-03-01 01:51:21 +01:00
cupcakearmy 8e7e0414a6
add copy to clipboard note 2022-03-01 01:16:31 +01:00
cupcakearmy 229c8d8368
update actix to version 4 2022-03-01 00:53:47 +01:00
cupcakearmy 1adf87b884
set memcached size 2022-03-01 00:53:34 +01:00
cupcakearmy a061b540b1
make top margin smaller 2022-01-21 17:46:13 +01:00
cupcakearmy 824603ff4a
add locales 2022-01-16 14:39:45 +01:00
cupcakearmy 539d99d35f
version bump 2022-01-16 14:04:49 +01:00
cupcakearmy 716034660c
translate the app 2022-01-16 14:02:53 +01:00
cupcakearmy bab65bcdad
bug due to dep update 2022-01-03 18:16:54 +01:00
cupcakearmy a0732a4593
move folder 2022-01-02 23:46:08 +01:00
cupcakearmy 835f7df0f6
update dep 2022-01-02 18:43:31 +01:00
173 changed files with 9908 additions and 4418 deletions

2
.dev.env Normal file
View File

@ -0,0 +1,2 @@
SIZE_LIMIT=10MiB
VERBOSITY=debug

View File

@ -1,2 +1,15 @@
target
node_modules
*
!/packages
!/package.json
!/pnpm-lock.yaml
!/pnpm-workspace.yaml
**/target
**/node_modules
**/dist
**/bin
**/*.tsbuildinfo
**/build
**/.svelte
**/.svelte-kit

1
.gitattributes vendored
View File

@ -1 +1,2 @@
*.afdesign filter=lfs diff=lfs merge=lfs -text
test/assets/** filter=lfs diff=lfs merge=lfs -text

BIN
.github/lokalise.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

View File

@ -1,41 +0,0 @@
name: ci
on:
workflow_dispatch:
push:
tags:
- "v*.*.*"
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
with:
install: true
- name: Docker Labels
id: meta
uses: crazy-max/ghaction-docker-meta@v2
with:
images: cupcakearmy/cryptgeon
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
id: docker_build
uses: docker/build-push-action@v2
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
- name: Image digest
run: echo ${{ steps.docker_build.outputs.digest }}

58
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,58 @@
name: Publish
on:
workflow_dispatch:
push:
tags:
- 'v*.*.*'
jobs:
cli:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v3
with:
cache: 'pnpm'
node-version-file: '.nvmrc'
registry-url: 'https://registry.npmjs.org'
- run: |
pnpm install --frozen-lockfile
pnpm run build
- run: npm publish
working-directory: ./packages/cli
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: docker/setup-qemu-action@v2
- uses: docker/setup-buildx-action@v2
with:
install: true
- name: Docker Labels
id: meta
uses: docker/metadata-action@v4
with:
images: cupcakearmy/cryptgeon
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v4
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}

39
.github/workflows/test.yaml vendored Normal file
View File

@ -0,0 +1,39 @@
name: Test
on:
push:
branches:
- main
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Node
- uses: pnpm/action-setup@v3
- uses: actions/setup-node@v4
with:
cache: 'pnpm'
node-version-file: '.nvmrc'
# Docker
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
with:
install: true
- name: Prepare
run: |
pnpm install --frozen-lockfile
pnpm exec playwright install --with-deps
pnpm run test:prepare
- name: Run your tests
run: pnpm test
- uses: actions/upload-artifact@v3
with:
name: test-results
path: test-results

19
.gitignore vendored
View File

@ -1,11 +1,10 @@
# Backend
/target
# Client
.DS_Store
node_modules
/.svelte
/build
/functions
.env
*.tsbuildinfo
node_modules
dist
bin
target
# Testing
test-results

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
v20.11.1

View File

@ -1,6 +0,0 @@
{
"cSpell.words": [
"ciphertext",
"cryptgeon"
]
}

View File

@ -5,6 +5,194 @@ 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.4.0] - 2023-11-01
### Changed
- Removed HTML sanitation, display the original message as string
- Links are now displayed under the note in a separate section
## [2.3.1] - 2023-06-23
### Added
- #92: Endpoint (`/api/live/`) for checking health status.
## [2.3.0] - 2023-05-30
### Added
- New CLI 🎉.
- Russian language.
- Option for reducing note id size (`ID_LENGTH`).
### Changed
- Moved to monorepo.
### Changed
- Default port is now 8000, not 5000.
- Moved to generic encryption library `occulto`.
### Fixed
- Bad chinese language code.
### Security
- Updated dependencies.
## [2.1.0] - 2023-01-04
### Added
- QR Code to more easily copy and share links.
## [2.0.7] - 2022-12-26
### Changed
- Svelte Kit now stable 🎉
## [2.0.6] - 2022-11-12
### Fixed
- #66 Set minimum a view.
### Security
- Updated dependencies.
## [2.0.5] - 2022-11-04
### Fixed
- Docker build pipeline.
## [2.0.4] - 2022-10-29
### Added
- `THEME_PAGE_TITLE`.
- `THEME_FAVICON`.
## [2.0.3] - 2022-10-07
### Added
- Flag for verbosity.
### Fixed
- #58 Fixed bug in the max views frontend form.
## [2.0.2] - 2022-07-20
### Added
- Toasts for events.
- E2E Tests.
- Make backend more configurable.
## [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.
### Changed
- Moved to redis.
- New html sanitizing library.
## [2.0.0-rc.0] - 2022-07-15
### Added
- Theming for logo and description text.
### Changed
- Moved to redis.
- New html sanitizing library.
## [1.5.3] - 2022-06-07
### Changed
- Use the value from the `MEMCACHE` env variable in startup script.
## [1.5.2] - 2022-06-07
### Added
- Wait for script for memecached.
### Security
- Updated dependencies.
## [1.5.1] - 2022-05-15
### Fixed
- Remove double note content.
## [1.5.0] - 2022-05-14
### Added
- Links in notes are not highlighted and can be directly clicked #30.
## [1.4.1] - 2022-03-05
### Fixed
- Router in prod build.
## [1.4.0] - 2022-03-02
### Added
- Support for multiple languages.
- Select multiple files without removing already selected ones.
- Tooltip for copy action.
- Configure maximum views, expiration and advanced options for the server.
### Changed
- Use native SVGs instead of images.
- Update robots.txt file to allow only root.
- Stronger frontend types.
## [1.3.3] - 2022-01-03
### Fixed
- Bug fix due to dependency update.
## [1.3.2] - 2022-01-02
### Changed
- Dependencies updates.
- Folder structure.
## [1.3.1] - 2021-12-30
### Added

2126
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,693 @@
{
"info": {
"_postman_id": "3aaeac19-4eac-4911-b3c8-912b17a48634",
"name": "Cryptgeon",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
},
"item": [
{
"name": "Notes",
"item": [
{
"name": "Preview",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{BASE}}/notes/:id",
"host": [
"{{BASE}}"
],
"path": [
"notes",
":id"
],
"variable": [
{
"key": "id",
"value": "{{NOTE_ID}}",
"description": "Id of the Note"
}
]
},
"description": "This endpoint is to query wether a note exists, without actually opening it. No view limits are used here, as contents of the note are not available, only the `meta` field is returned, which is public."
},
"response": [
{
"name": "200",
"originalRequest": {
"method": "GET",
"header": [],
"url": {
"raw": "{{BASE}}/notes/:id",
"host": [
"{{BASE}}"
],
"path": [
"notes",
":id"
],
"variable": [
{
"key": "id",
"value": "{{NOTE_ID}}",
"description": "Id of the Note"
}
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "transfer-encoding",
"value": "chunked"
},
{
"key": "connection",
"value": "close"
},
{
"key": "content-type",
"value": "application/json"
},
{
"key": "content-encoding",
"value": "gzip"
},
{
"key": "vary",
"value": "accept-encoding"
},
{
"key": "date",
"value": "Tue, 23 May 2023 05:24:29 GMT"
}
],
"cookie": [],
"body": "{}"
},
{
"name": "404",
"originalRequest": {
"method": "GET",
"header": [],
"url": {
"raw": "{{BASE}}/notes/:id",
"host": [
"{{BASE}}"
],
"path": [
"notes",
":id"
],
"variable": [
{
"key": "id",
"value": "{{NOTE_ID}}",
"description": "Id of the Note"
}
]
}
},
"status": "Not Found",
"code": 404,
"_postman_previewlanguage": "plain",
"header": [
{
"key": "transfer-encoding",
"value": "chunked"
},
{
"key": "connection",
"value": "close"
},
{
"key": "vary",
"value": "accept-encoding"
},
{
"key": "content-encoding",
"value": "gzip"
},
{
"key": "date",
"value": "Tue, 23 May 2023 05:25:26 GMT"
}
],
"cookie": [],
"body": ""
}
]
},
{
"name": "Create",
"event": [
{
"listen": "test",
"script": {
"exec": [
"const jsonData = pm.response.json();",
"pm.collectionVariables.set('NOTE_ID', jsonData.id)",
""
],
"type": "text/javascript"
}
}
],
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"contents\": \"Some encrypted content\",\n \"views\": 1,\n \"meta\": \"{\\\"type\\\":\\\"text\\\"}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{BASE}}/notes/",
"host": [
"{{BASE}}"
],
"path": [
"notes",
""
]
}
},
"response": [
{
"name": "Simple",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"contents\": \"Some encrypted content\",\n \"views\": 1,\n \"meta\": \"{\\\"type\\\":\\\"text\\\"}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{BASE}}/notes/",
"host": [
"{{BASE}}"
],
"path": [
"notes",
""
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "transfer-encoding",
"value": "chunked"
},
{
"key": "connection",
"value": "close"
},
{
"key": "content-encoding",
"value": "gzip"
},
{
"key": "content-type",
"value": "application/json"
},
{
"key": "vary",
"value": "accept-encoding"
},
{
"key": "date",
"value": "Tue, 23 May 2023 05:31:54 GMT"
}
],
"cookie": [],
"body": "{\n \"id\": \"1QeEWDQbQY9dOo8cDDQjykaEjouqugTR6A78sjgn4VMv\"\n}"
},
{
"name": "5 Minutes",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"contents\": \"Some encrypted content\",\n \"expiration\": 5,\n \"meta\": \"{\\\"type\\\":\\\"text\\\"}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{BASE}}/notes/",
"host": [
"{{BASE}}"
],
"path": [
"notes",
""
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "transfer-encoding",
"value": "chunked"
},
{
"key": "connection",
"value": "close"
},
{
"key": "content-encoding",
"value": "gzip"
},
{
"key": "content-type",
"value": "application/json"
},
{
"key": "vary",
"value": "accept-encoding"
},
{
"key": "date",
"value": "Tue, 23 May 2023 05:31:54 GMT"
}
],
"cookie": [],
"body": "{\n \"id\": \"1QeEWDQbQY9dOo8cDDQjykaEjouqugTR6A78sjgn4VMv\"\n}"
},
{
"name": "3 Views",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"contents\": \"Some encrypted content\",\n \"views\": 3,\n \"meta\": \"{\\\"type\\\":\\\"text\\\"}\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{BASE}}/notes/",
"host": [
"{{BASE}}"
],
"path": [
"notes",
""
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "transfer-encoding",
"value": "chunked"
},
{
"key": "connection",
"value": "close"
},
{
"key": "content-encoding",
"value": "gzip"
},
{
"key": "content-type",
"value": "application/json"
},
{
"key": "vary",
"value": "accept-encoding"
},
{
"key": "date",
"value": "Tue, 23 May 2023 05:31:54 GMT"
}
],
"cookie": [],
"body": "{\n \"id\": \"1QeEWDQbQY9dOo8cDDQjykaEjouqugTR6A78sjgn4VMv\"\n}"
}
]
},
{
"name": "Read",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{BASE}}/notes/:id",
"host": [
"{{BASE}}"
],
"path": [
"notes",
":id"
],
"variable": [
{
"key": "id",
"value": "{{NOTE_ID}}"
}
]
},
"description": "This endpoint gets the actual contents of a note. It's a `DELETE` endpoint, es it decreases the `view` counter, and deletes the note if `0` is reached."
},
"response": [
{
"name": "200",
"originalRequest": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{BASE}}/notes/:id",
"host": [
"{{BASE}}"
],
"path": [
"notes",
":id"
],
"variable": [
{
"key": "id",
"value": "{{NOTE_ID}}"
}
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "transfer-encoding",
"value": "chunked"
},
{
"key": "connection",
"value": "close"
},
{
"key": "content-type",
"value": "application/json"
},
{
"key": "vary",
"value": "accept-encoding"
},
{
"key": "content-encoding",
"value": "gzip"
},
{
"key": "date",
"value": "Tue, 23 May 2023 05:59:07 GMT"
}
],
"cookie": [],
"body": "{\n \"meta\": \"{\\\"type\\\":\\\"text\\\"}\",\n \"contents\": \"Some encrypted content\"\n}"
},
{
"name": "404",
"originalRequest": {
"method": "DELETE",
"header": [],
"url": {
"raw": "{{BASE}}/notes/:id",
"host": [
"{{BASE}}"
],
"path": [
"notes",
":id"
],
"variable": [
{
"key": "id",
"value": "{{NOTE_ID}}"
}
]
}
},
"status": "Not Found",
"code": 404,
"_postman_previewlanguage": "plain",
"header": [
{
"key": "transfer-encoding",
"value": "chunked"
},
{
"key": "connection",
"value": "close"
},
{
"key": "vary",
"value": "accept-encoding"
},
{
"key": "content-encoding",
"value": "gzip"
},
{
"key": "date",
"value": "Tue, 23 May 2023 05:59:15 GMT"
}
],
"cookie": [],
"body": ""
}
]
}
]
},
{
"name": "Status",
"item": [
{
"name": "Get server status",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{BASE}}/status/",
"host": [
"{{BASE}}"
],
"path": [
"status",
""
]
}
},
"response": [
{
"name": "200",
"originalRequest": {
"method": "GET",
"header": [],
"url": {
"raw": "{{BASE}}/status/",
"host": [
"{{BASE}}"
],
"path": [
"status",
""
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "transfer-encoding",
"value": "chunked"
},
{
"key": "connection",
"value": "close"
},
{
"key": "content-encoding",
"value": "gzip"
},
{
"key": "vary",
"value": "accept-encoding"
},
{
"key": "content-type",
"value": "application/json"
},
{
"key": "date",
"value": "Tue, 23 May 2023 05:56:45 GMT"
}
],
"cookie": [],
"body": "{\n \"version\": \"2.3.0-beta.4\",\n \"max_size\": 10485760,\n \"max_views\": 100,\n \"max_expiration\": 360,\n \"allow_advanced\": true,\n \"theme_image\": \"\",\n \"theme_text\": \"\",\n \"theme_page_title\": \"\",\n \"theme_favicon\": \"\"\n}"
}
]
},
{
"name": "Health Check",
"request": {
"method": "GET",
"header": [],
"url": {
"raw": "{{BASE}}/live/",
"host": [
"{{BASE}}"
],
"path": [
"live",
""
]
},
"description": "Return `200` for healthy service. `503` if service is unavailable."
},
"response": [
{
"name": "Healthy",
"originalRequest": {
"method": "GET",
"header": [],
"url": {
"raw": "{{BASE}}/live/",
"host": [
"{{BASE}}"
],
"path": [
"live",
""
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "plain",
"header": [
{
"key": "transfer-encoding",
"value": "chunked"
},
{
"key": "vary",
"value": "accept-encoding"
},
{
"key": "content-encoding",
"value": "gzip"
},
{
"key": "date",
"value": "Thu, 22 Jun 2023 20:17:58 GMT"
}
],
"cookie": [],
"body": null
},
{
"name": "Service Unavilable",
"originalRequest": {
"method": "GET",
"header": [],
"url": {
"raw": "{{BASE}}/live/",
"host": [
"{{BASE}}"
],
"path": [
"live",
""
]
}
},
"status": "Service Unavailable",
"code": 503,
"_postman_previewlanguage": "plain",
"header": [
{
"key": "transfer-encoding",
"value": "chunked"
},
{
"key": "content-encoding",
"value": "gzip"
},
{
"key": "vary",
"value": "accept-encoding"
},
{
"key": "date",
"value": "Thu, 22 Jun 2023 20:18:55 GMT"
}
],
"cookie": [],
"body": null
}
]
}
]
}
],
"event": [
{
"listen": "prerequest",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
},
{
"listen": "test",
"script": {
"type": "text/javascript",
"exec": [
""
]
}
}
],
"variable": [
{
"key": "BASE",
"value": "http://localhost:1234/api",
"type": "default"
},
{
"key": "NOTE_ID",
"value": "",
"type": "default"
}
]
}

View File

@ -1,29 +1,30 @@
FROM node:16-alpine as CLIENT
# FRONTEND
FROM node:20-alpine as client
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /tmp
COPY ./client ./
RUN npm install -g pnpm
RUN pnpm install
COPY . .
RUN pnpm install --frozen-lockfile
RUN pnpm run build
FROM rust:1.51-alpine as RUST
# BACKEND
FROM rust:1.76-alpine as backend
WORKDIR /tmp
RUN apk add libc-dev openssl-dev alpine-sdk
COPY ./Cargo* ./
COPY ./src ./src
RUN apk add --no-cache libc-dev openssl-dev alpine-sdk
COPY ./packages/backend ./
RUN cargo build --release
FROM scratch
# RUNNER
FROM alpine:3.19
WORKDIR /app
COPY --from=RUST /tmp/target/release/cryptgeon .
COPY --from=CLIENT /tmp/build ./client/build
ENV MEMCACHE=memcached:11211
EXPOSE 5000
RUN apk add --no-cache curl
COPY --from=backend /tmp/target/release/cryptgeon .
COPY --from=client /tmp/packages/frontend/build ./frontend
ENV FRONTEND_PATH="./frontend"
ENV REDIS="redis://redis/"
EXPOSE 8000
ENTRYPOINT [ "/app/cryptgeon" ]

181
README.md
View File

@ -9,20 +9,39 @@
<img alt="Latest version" src="https://img.shields.io/github/v/release/cupcakearmy/cryptgeon?style=for-the-badge" />
</a>
<br/>
<a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<br/>
<br/><br/>
<a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" height="50" /></a>
<a href=""><img src="./.github/lokalise.png" height="50">
<br/><br/>
EN | [简体中文](README_zh-CN.md) | [ES](README_ES.md)
## About?
_cryptgeon_ is a secure, open source sharing note or file service inspired by [_PrivNote_](https://privnote.com)
_cryptgeon_ is a secure, open source sharing note or file service inspired by [_PrivNote_](https://privnote.com).
It includes a server, a web page and a CLI client.
## Demo
> 🌍 If you want to translate the project feel free to reach out to me.
>
> Thanks to [Lokalise](https://lokalise.com/) for providing free access to their platform.
Check out the demo and see for yourself https://cryptgeon.nicco.io.
## Live Service / Demo
### Web
Check out the live service / demo and see for yourself [cryptgeon.org](https://cryptgeon.org)
### CLI
```
npx cryptgeon send text "This is a secret note"
```
For more documentation about the CLI see the [readme](./packages/cli/README.md).
## Features
- send text or files
- server cannot decrypt contents due to client side encryption
- view or time constraints
- in memory, no persistence
@ -43,14 +62,27 @@ of the notes even if it tried to.
## Environment Variables
| Variable | Default | Description |
| ------------ | ----------------- | --------------------------------------------------------------------------------------- |
| `MEMCACHE` | `memcached:11211` | Memcached URL to connect to. |
| `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/) |
| Variable | Default | Description |
| ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `REDIS` | `redis://redis/` | Redis URL to connect to. [According to format](https://docs.rs/redis/latest/redis/#connection-parameters) |
| `SIZE_LIMIT` | `1 KiB` | Max size for body. Accepted values according to [byte-unit](https://docs.rs/byte-unit/). <br> `512 MiB` is the maximum allowed. <br> 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. |
| `ALLOW_FILES` | `true` | Allow uploading files. If set to `false`, users will only be allowed to create text notes. |
| `THEME_NEW_NOTE_NOTICE` | `true` | Show the message about how notes are stored in the memory and may be evicted after creating a new note. Defaults to `true`. |
| `ID_LENGTH` | `32` | Set the size of the note `id` in bytes. By default this is `32` bytes. This is useful for reducing link size. _This setting does not affect encryption strength_. |
| `VERBOSITY` | `warn` | Verbosity level for the backend. [Possible values](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) are: `error`, `warn`, `info`, `debug`, `trace` |
| `THEME_IMAGE` | `""` | Custom image for replacing the logo. Must be publicly reachable |
| `THEME_TEXT` | `""` | Custom text for replacing the description below the logo |
| `THEME_PAGE_TITLE` | `""` | Custom text the page title |
| `THEME_FAVICON` | `""` | Custom url for the favicon. Must be publicly reachable |
## Deployment
`https` is required otherwise browsers will not support the cryptographic functions.
> `https` is required otherwise browsers will not support the cryptographic functions.
> There is a health endpoint available at `/api/health/`. It returns either 200 or 503.
### Docker
@ -59,21 +91,32 @@ Docker is the easiest way. There is the [official image here](https://hub.docker
```yaml
# docker-compose.yml
version: '3.7'
version: '3.8'
services:
memcached:
image: memcached:1-alpine
entrypoint: memcached -m 128M -I 4M # Limit to 128 MB Ram, 4M per entry, customize at free will.
redis:
image: redis:7-alpine
# Set a size limit. See link below on how to customise.
# https://redis.io/docs/manual/eviction/
# command: redis-server --maxmemory 1gb --maxmemory-policy allkeys-lru
app:
image: cupcakearmy/cryptgeon:latest
depends_on:
- memcached
- redis
environment:
SIZE_LIMIT: 4M
# Size limit for a single note.
SIZE_LIMIT: 4 MiB
ports:
- 80:5000
- 80:8000
# Optional health checks
# healthcheck:
# test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/api/live/"]
# interval: 1m
# timeout: 3s
# retries: 2
# start_period: 5s
```
### NGINX Proxy
@ -82,55 +125,81 @@ See the [examples/nginx](https://github.com/cupcakearmy/cryptgeon/tree/main/exam
### Traefik 2
Assumptions:
See the [examples/traefik](https://github.com/cupcakearmy/cryptgeon/tree/main/examples/traefik) folder.
- External proxy docker network `proxy`
- A certificate resolver `le`
- A https entrypoint `secure`
- Domain name `example.org`
### Scratch
```yaml
version: '3.8'
See the [examples/scratch](https://github.com/cupcakearmy/cryptgeon/tree/main/examples/scratch) folder. There you'll find a guide how to setup a server and install cryptgeon from scratch.
networks:
proxy:
external: true
### Synology
services:
memcached:
image: memcached:1-alpine
restart: unless-stopped
entrypoint: memcached -m 128M -I 4M # Limit to 128 MB Ram, 4M per entry, customize at free will.
There is a [guide](https://mariushosting.com/how-to-install-cryptgeon-on-your-synology-nas/) you can follow.
app:
image: cupcakearmy/cryptgeon:latest
restart: unless-stopped
depends_on:
- memcached
networks:
- default
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.cryptgeon.rule=Host(`example.org`)
- traefik.http.routers.cryptgeon.entrypoints=secure
- traefik.http.routers.cryptgeon.tls.certresolver=le
```
### YouTube Guides
- English by [Webnestify](https://www.youtube.com/watch?v=XAyD42I7wyI)
- English by [DB Tech](https://www.youtube.com/watch?v=S0jx7wpOfNM) [Previous Video](https://www.youtube.com/watch?v=JhpIatD06vE)
- German by [ApfelCast](https://www.youtube.com/watch?v=84ZMbE9AkHg)
## Development
1. Clone
2. run `pnpm i` in the root and and client `client/` folders.
3. Run `pnpm run dev` to start development.
**Requirements**
Running `npm run dev` in the root folder will start the following things
- `pnpm`: `>=6`
- `node`: `>=18`
- `rust`: edition `2021`
- a memcache docker container
- rust backend with hot reload
- client with hot reload
**Install**
```bash
pnpm install
# Also you need cargo watch if you don't already have it installed.
# https://lib.rs/crates/cargo-watch
cargo install cargo-watch
```
**Run**
Make sure you have docker running.
```bash
pnpm run dev
```
Running `pnpm run dev` in the root folder will start the following things:
- redis docker container
- rust backend
- client
- cli
You can see the app under [localhost:1234](http://localhost:1234).
###### Attributions
> There is a Postman collection with some example requests [available in the repo](./Cryptgeon.postman_collection.json)
Icons made by <a href="https://www.freepik.com" title="Freepik">freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>
### Tests
Tests are end to end tests written with Playwright.
```sh
pnpm run test:prepare
# Use the test or test:local script. The local version only runs in one browser for quicker development.
pnpm run test:local
```
## Security
Please refer to the security section [here](./SECURITY.md).
---
_Attributions_
- Test data:
- Text for tests [Nietzsche Ipsum](https://nietzsche-ipsum.com/)
- [AES Paper](https://www.cs.miami.edu/home/burt/learning/Csc688.012/rijndael/rijndael_doc_V2.pdf)
- [Unsplash Pictures](https://unsplash.com/)
- Loading animation by [Nikhil Krishnan](https://codepen.io/nikhil8krishnan/pen/rVoXJa)
- Icons made by <a href="https://www.freepik.com" title="Freepik">freepik</a> from <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>

200
README_ES.md Normal file
View File

@ -0,0 +1,200 @@
<p align="center">
<img src="./design/Github.png" alt="logo">
</p>
<a href="https://discord.gg/nuby6RnxZt">
<img alt="discord" src="https://img.shields.io/discord/252403122348097536?style=for-the-badge" />
<img alt="docker pulls" src="https://img.shields.io/docker/pulls/cupcakearmy/cryptgeon?style=for-the-badge" />
<img alt="Docker image size badge" src="https://img.shields.io/docker/image-size/cupcakearmy/cryptgeon?style=for-the-badge" />
<img alt="Latest version" src="https://img.shields.io/github/v/release/cupcakearmy/cryptgeon?style=for-the-badge" />
</a>
<br/><br/>
<a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" height="50" /></a>
<a href=""><img src="./.github/lokalise.png" height="50">
<br/><br/>
[EN](README.md) | [简体中文](README_zh-CN.md) | ES
## Acerca de
_cryptgeon_ es un servicio seguro y de código abierto para compartir notas o archivos inspirado en [_PrivNote_](https://privnote.com).
Incluye un servidor, una página web y una interfaz de línea de comandos (CLI, por sus siglas en inglés).
> 🌍 Si quieres traducir este proyecto no dudes en ponerte en contacto conmigo.
>
> Gracias a [Lokalise](https://lokalise.com/) por darnos acceso gratis a su plataforma.
## Demo
### Web
Prueba la demo y experimenta por ti mismo [cryptgeon.org](https://cryptgeon.org)
### CLI
```
npx cryptgeon send text "Esto es una nota secreta"
```
Puedes revisar la documentación sobre el CLI en este [readme](./packages/cli/README.md).
## Características
- enviar texto o archivos
- el servidor no puede desencriptar el contenido debido a que la encriptación se hace del lado del cliente
- restriccion de vistas o de tiempo
- en memoria, sin persistencia
- compatibilidad obligatoria con el modo oscuro
## ¿Cómo funciona?
Se genera una <code>id (256bit)</code> y una <code>llave 256(bit)</code> para cada nota. La
<code>id</code>
se usa para guardar y recuperar la nota. Después la nota es encriptada con la <code>llave</code> y con aes en modo gcm del lado del cliente y por último se envía al servidor. La información es almacenada en memoria y nunca persiste en el disco. El servidor nunca ve la llave de encriptación por lo que no puede desencriptar el contenido de las notas aunque lo intentara.
## Capturas de pantalla
![screenshot](./design/Screens.png)
## Variables de entorno
| Variable | Default | Descripción |
| ------------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `REDIS` | `redis://redis/` | Redis URL a la que conectarse. [Según el formato](https://docs.rs/redis/latest/redis/#connection-parameters) |
| `SIZE_LIMIT` | `1 KiB` | Tamaño máximo. Valores aceptados según la [unidad byte](https://docs.rs/byte-unit/). <br> `512 MiB` es el máximo permitido. <br> El frontend mostrará ese número, incluyendo el ~35% de sobrecarga de codificación. |
| `MAX_VIEWS` | `100` | Número máximo de vistas. |
| `MAX_EXPIRATION` | `360` | Tiempo máximo de expiración en minutos. |
| `ALLOW_ADVANCED` | `true` | Permitir configuración personalizada. Si se establece en `false` todas las notas serán de una sola vista. |
| `ID_LENGTH` | `32` | Establece el tamaño en bytes de la `id` de la nota. Por defecto es de `32` bytes. Esto es util para reducir el tamaño del link. _Esta configuración no afecta el nivel de encriptación_. |
| `VERBOSITY` | `warn` | Nivel de verbosidad del backend. [Posibles valores](https://docs.rs/env_logger/latest/env_logger/#enabling-logging): `error`, `warn`, `info`, `debug`, `trace` |
| `THEME_IMAGE` | `""` | Imagen personalizada para reemplazar el logo. Debe ser accesible públicamente. |
| `THEME_TEXT` | `""` | Texto personalizado para reemplazar la descripción bajo el logo. |
| `THEME_PAGE_TITLE` | `""` | Texto personalizado para el título |
| `THEME_FAVICON` | `""` | Url personalizada para el favicon. Debe ser accesible públicamente. |
## Despliegue
> Se requiere `https` de lo contrario el navegador no soportará las funciones de encriptacón.
> Hay un endpoint para verificar el estado, lo encontramos en `/api/health/`. Regresa un código 200 o 503.
### Docker
Docker es la manera más fácil. Aquí encontramos [la imágen oficial](https://hub.docker.com/r/cupcakearmy/cryptgeon).
```yaml
# docker-compose.yml
version: '3.8'
services:
redis:
image: redis:7-alpine
# Set a size limit. See link below on how to customise.
# https://redis.io/docs/manual/eviction/
# command: redis-server --maxmemory 1gb --maxmemory-policy allkeys-lru
app:
image: cupcakearmy/cryptgeon:latest
depends_on:
- redis
environment:
# Size limit for a single note.
SIZE_LIMIT: 4 MiB
ports:
- 80:8000
# Optional health checks
# healthcheck:
# test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/api/live/"]
# interval: 1m
# timeout: 3s
# retries: 2
# start_period: 5s
```
### NGINX Proxy
Ver la carpeta de [ejemplo/nginx](https://github.com/cupcakearmy/cryptgeon/tree/main/examples/nginx). Hay un ejemplo con un proxy simple y otro con https. Es necesario que especifiques el nombre del servidor y los certificados.
### Traefik 2
Ver la carpeta de [ejemplo/traefik](https://github.com/cupcakearmy/cryptgeon/tree/main/examples/traefik).
### Scratch
Ver la carpeta de [ejemplo/scratch](https://github.com/cupcakearmy/cryptgeon/tree/main/examples/scratch). Ahí encontrarás una guía de cómo configurar el servidor e instalar cryptgeon desde cero.
### Synology
Hay una [guía](https://mariushosting.com/how-to-install-cryptgeon-on-your-synology-nas/) (en inglés) que puedes seguir.
### Guías en Youtube
- En inglés, por [Webnestify](https://www.youtube.com/watch?v=XAyD42I7wyI)
- En inglés, por [DB Tech](https://www.youtube.com/watch?v=S0jx7wpOfNM) [Previous Video](https://www.youtube.com/watch?v=JhpIatD06vE)
- En alemán, por [ApfelCast](https://www.youtube.com/watch?v=84ZMbE9AkHg)
## Desarrollo
**Requisitos**
- `pnpm`: `>=6`
- `node`: `>=18`
- `rust`: edition `2021`
**Instalación**
```bash
pnpm install
# También necesitas cargo-watch, si no lo tienes instalado.
# https://lib.rs/crates/cargo-watch
cargo install cargo-watch
```
**Ejecutar**
Asegurate de que docker se esté ejecutando.
```bash
pnpm run dev
```
Ejecutando `pnpm run dev` en la carpeta raíz iniciará lo siguiente:
- redis docker container
- rust backend
- client
- cli
Puedes ver la app en [localhost:1234](http://localhost:1234).
> Existe una colección de Postman con algunas peticiones de ejemplo [disponible en el repo](./Cryptgeon.postman_collection.json)
### Tests
Los tests son end-to-end tests escritos con Playwright.
```sh
pnpm run test:prepare
# Usa el script test o test:local. La versión local solo corre en el navegador para acelerar el desarrollo.
pnpm run test:local
```
## Seguridad
Por favor dirigite a la sección de seguridad [aquí](./SECURITY.md).
---
_Atribuciones_
- Datos del Test:
- Texto para los tests [Nietzsche Ipsum](https://nietzsche-ipsum.com/)
- [AES Paper](https://www.cs.miami.edu/home/burt/learning/Csc688.012/rijndael/rijndael_doc_V2.pdf)
- [Unsplash Imágenes](https://unsplash.com/)
- Animación de carga por [Nikhil Krishnan](https://codepen.io/nikhil8krishnan/pen/rVoXJa)
- Iconos hechos por <a href="https://www.freepik.com" title="Freepik">freepik</a> de <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a>

184
README_zh-CN.md Normal file
View File

@ -0,0 +1,184 @@
<p align="center">
<img src="./design/Github_zh-CN.png" alt="logo">
</p>
<a href="https://discord.gg/nuby6RnxZt">
<img alt="discord" src="https://img.shields.io/discord/252403122348097536?style=for-the-badge" />
<img alt="docker pulls" src="https://img.shields.io/docker/pulls/cupcakearmy/cryptgeon?style=for-the-badge" />
<img alt="Docker image size badge" src="https://img.shields.io/docker/image-size/cupcakearmy/cryptgeon?style=for-the-badge" />
<img alt="Latest version" src="https://img.shields.io/github/v/release/cupcakearmy/cryptgeon?style=for-the-badge" />
</a>
<br/>
<a href="https://www.producthunt.com/posts/cryptgeon?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-cryptgeon" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=295189&theme=light" alt="Cryptgeon - Securely share self-destructing notes | Product Hunt" height="50" /></a>
<a href=""><img src="./.github/lokalise.png" height="50">
<br/>
[EN](README.md) | 简体中文 | [ES](README_ES.md)
## 关于本项目
_加密鸽_ 是一个受 [_PrivNote_](https://privnote.com)项目启发的安全、开源共享密信和文件共享服务器
> 🌍 如果你想翻译此项目请随时与我联系.
>
> 感谢 [Lokalise](https://lokalise.com/) 提供免费的平台服务支持
## 演示示例
查看加密鸽的在线演示 demo [cryptgeon.org](https://cryptgeon.org)
## 功能
- 服务端无法解密和查看客户端加密的内容
- 查看次数或时间限制,阅后即焚
- 您发送的数据将存放于内存中,不会写入到磁盘中
- 黑暗模式支持
## 加密鸽是如何工作的?
加密鸽会为每条笔记都生成一个独立的 <code>id (256bit)</code><code>key 256(bit)</code>
其中<code>id</code>用于保存和提取密信, 在这之后这封密信将会被客户端使用 AES 算法的 GCM 模式和`key`进行加密然后发送至服务器,数据将会保存在服务器的内存中且永远不会被持久化到硬盘上,服务端永远不会得到密钥并且无法解读密信的内容。
## 屏幕截图
![screenshot](./design/Screens.png)
## 环境变量
| 变量名称 | 默认值 | 描述 |
| ----------------- | ---------------- | --------------------------------------------------------------------------------- |
| `REDIS` | `redis://redis/` | Redis 连接 URL。 |
| `SIZE_LIMIT` | `1 KiB` | 最大请求体(body)限制。有关支持的数值请查看 [字节单位](https://docs.rs/byte-unit/) |
| `MAX_VIEWS` | `100` | 密信最多查看次数限制 |
| ` MAX_EXPIRATION` | `360` | 密信最长过期时间限制(分钟) |
| `ALLOW_ADVANCED` | `true` | 是否允许自定义设置,该项如果设为`false`,则不会显示自定义设置模块 |
| `THEME_IMAGE` | `""` | 自定义 Logo 图片,你在这里填写的的图片链接必须是可以公开访问的。 |
| `THEME_TEXT` | `""` | 自定义在 Logo 下方的文本。 |
## 部署
加密鸽必须使用`https`,否则浏览器可能将不会支援加密鸽的加密算法。
### Docker
Docker 是最简单的部署方式。这里是[官方镜像的地址](https://hub.docker.com/r/cupcakearmy/cryptgeon)。
附:译者的[部署笔记](https://www.hash070.top/archives/cryptgeon-docker-deploy.html)
```yaml
# docker-compose.yml
version: '3.8'
services:
redis:
image: redis:7-alpine
app:
image: cupcakearmy/cryptgeon:latest
depends_on:
- redis
environment:
SIZE_LIMIT: 4 MiB
ports:
- 80:8000
```
### NGINX 反向代理
查看 [examples/nginx](https://github.com/cupcakearmy/cryptgeon/tree/main/examples/nginx) 目录。那里有几个示例反代配置文件模板,其中一个是带 https 配置的反代配置模板,你需要指定服务器的名称和证书才能生效。
### Traefik 2
假设:
- 外部 Docker 代理网络 `proxy`
- 证书解析器 `le`
- 一个 https 入站点 `secure`
- 域名 `example.org`
```yaml
version: '3.8'
networks:
proxy:
external: true
services:
redis:
image: redis:7-alpine
restart: unless-stopped
app:
image: cupcakearmy/cryptgeon:latest
restart: unless-stopped
depends_on:
- redis
networks:
- default
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.cryptgeon.rule=Host(`example.org`)
- traefik.http.routers.cryptgeon.entrypoints=secure
- traefik.http.routers.cryptgeon.tls.certresolver=le
```
## 开发
**环境要求**
- `pnpm`: `>=6`
- `node`: `>=14`
- `rust`: edition `2021`
**安装**
```bash
pnpm install
pnpm --prefix frontend install
# 你还需要安装CargoWatch.
# https://lib.rs/crates/cargo-watch
cargo install cargo-watch
```
**运行**
确保你的 Docker 正在运行
```bash
pnpm run dev
```
在根目录执行 `pnpm run dev` 会开启下列服务:
- 一个 redis docker 容器
- 无热重载的 rust 后端
- 可热重载的客户端
你可以通过 1234 端口进入该应用,即 [localhost:1234](http://localhost:1234).
## 测试
这些测试是用 Playwright 实现的一些端到端测试用例。
```sh
pnpm run test:prepare
docker compose up redis -d
pnpm run test:server
# 在另一个终端中:
# 使用test或者test:local script。为了更快的开发本地版本只会在一个浏览器中运行。
pnpm run test:local
```
###### Attributions
- 测试数据:
- 测试文本 [Nietzsche Ipsum](https://nietzsche-ipsum.com/)
- [AES Paper](https://www.cs.miami.edu/home/burt/learning/Csc688.012/rijndael/rijndael_doc_V2.pdf)
- [Unsplash Pictures](https://unsplash.com/)
- 加载动画由 [Nikhil Krishnan](https://codepen.io/nikhil8krishnan/pen/rVoXJa) 提供
- 图标由来自 <a href="https://www.flaticon.com/" title="Flaticon">www.flaticon.com</a><a href="https://www.freepik.com" title="Freepik">freepik</a> 提供

18
SECURITY.md Normal file
View File

@ -0,0 +1,18 @@
# Security Policy
## Supported Versions
Please ensure that you are using the latest major version available.
| Version | Supported |
| ------- | --------- |
| 2.x | ✅ |
| < 1.x | |
## Reporting a vulnerability
_cryptgeon_ has a full disclosure vulnerability policy.
Report any bug / vulnerability directly to the [issue tracker](https://github.com/cupcakearmy/cryptgeon/issues).
Please do NOT attempt to report any security vulnerability in this code privately to anybody.
> Shamefully copied of the [ring security section](https://github.com/briansmith/ring#bug-reporting).

View File

@ -1,5 +0,0 @@
.DS_Store
node_modules
/.svelte
/build
/functions

View File

@ -1 +0,0 @@
engine-strict=true

View File

@ -1,38 +0,0 @@
# create-svelte
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte);
## Creating a project
If you're seeing this, you've probably already done this step. Congrats!
```bash
# create a new project in the current directory
npm init svelte@next
# create a new project in my-app
npm init svelte@next my-app
```
> Note: the `@next` is temporary
## Developing
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
Before creating a production version of your app, install an [adapter](https://kit.svelte.dev/docs#adapters) for your target environment. Then:
```bash
npm run build
```
> You can preview the built app with `npm run preview`, regardless of whether you installed an adapter. This should _not_ be used to serve your app in production.

View File

@ -1,7 +0,0 @@
├─ MIT: 46
├─ MIT*: 2
├─ BSD-3-Clause: 2
├─ ISC: 1
├─ 0BSD: 1
└─ Apache-2.0: 1
1 ├─ MIT: 46
2 ├─ MIT*: 2
3 ├─ BSD-3-Clause: 2
4 ├─ ISC: 1
5 ├─ 0BSD: 1
6 └─ Apache-2.0: 1

View File

@ -1,25 +0,0 @@
{
"private": true,
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
"preview": "svelte-kit preview",
"licenses": "license-checker --summary > licenses.csv"
},
"type": "module",
"devDependencies": {
"@sveltejs/adapter-static": "^1.0.0-next.22",
"@sveltejs/kit": "^1.0.0-next.202",
"svelte": "^3.44.3",
"svelte-preprocess": "^4.10.1",
"tslib": "^2.3.1",
"typescript": "^4.5.4",
"vite": "^2.7.4"
},
"dependencies": {
"@fontsource/fira-mono": "^4.5.0",
"copy-to-clipboard": "^3.3.1",
"file-saver": "^2.0.5",
"pretty-bytes": "^5.6.0"
}
}

View File

@ -1,638 +0,0 @@
lockfileVersion: 5.3
specifiers:
'@fontsource/fira-mono': ^4.5.0
'@sveltejs/adapter-static': ^1.0.0-next.22
'@sveltejs/kit': ^1.0.0-next.202
copy-to-clipboard: ^3.3.1
file-saver: ^2.0.5
pretty-bytes: ^5.6.0
svelte: ^3.44.3
svelte-preprocess: ^4.10.1
tslib: ^2.3.1
typescript: ^4.5.4
vite: ^2.7.4
dependencies:
'@fontsource/fira-mono': 4.5.0
copy-to-clipboard: 3.3.1
file-saver: 2.0.5
pretty-bytes: 5.6.0
devDependencies:
'@sveltejs/adapter-static': 1.0.0-next.22
'@sveltejs/kit': 1.0.0-next.202_svelte@3.44.3
svelte: 3.44.3
svelte-preprocess: 4.10.1_svelte@3.44.3+typescript@4.5.4
tslib: 2.3.1
typescript: 4.5.4
vite: 2.7.4
packages:
/@fontsource/fira-mono/4.5.0:
resolution: {integrity: sha512-KE+d3wmgq/YKM0BqgUF7p2yeBNi805Nfof1lC1wJ7E9i2EWoC363sGdKG+MQBVm+ei3GYZu+Bo8Xha1w1pkB7g==}
dev: false
/@rollup/pluginutils/4.1.2:
resolution: {integrity: sha512-ROn4qvkxP9SyPeHaf7uQC/GPFY6L/OWy9+bd9AwcjOAWQwxRscoEyAUD8qCY5o5iL4jqQwoLk2kaTKJPb/HwzQ==}
engines: {node: '>= 8.0.0'}
dependencies:
estree-walker: 2.0.2
picomatch: 2.3.0
dev: true
/@sveltejs/adapter-static/1.0.0-next.22:
resolution: {integrity: sha512-Dc1V9Z72dA7caVwNxxzl9Jhcq4uN9N1udA2GKNTLMu3aWX3Cq+v6C2CddY9Aazr+F9h6J0vi9AienuH+ySRXzQ==}
dev: true
/@sveltejs/kit/1.0.0-next.202_svelte@3.44.3:
resolution: {integrity: sha512-rXmJ0FplkWvD1CaeCfejRYhOJYrlmeUm5Fkw7gIKDdWPQev5rqOhd9B9ZvRpq35oMqCAwaOfK+e5S6k+83feEQ==}
engines: {node: '>=14.13'}
hasBin: true
peerDependencies:
svelte: ^3.44.0
dependencies:
'@sveltejs/vite-plugin-svelte': 1.0.0-next.32_svelte@3.44.3+vite@2.7.4
cheap-watch: 1.0.4
sade: 1.7.4
svelte: 3.44.3
vite: 2.7.4
transitivePeerDependencies:
- diff-match-patch
- less
- sass
- stylus
- supports-color
dev: true
/@sveltejs/vite-plugin-svelte/1.0.0-next.32_svelte@3.44.3+vite@2.7.4:
resolution: {integrity: sha512-Lhf5BxVylosHIW6U2s6WDQA39ycd+bXivC8gHsXCJeLzxoHj7Pv7XAOk25xRSXT4wHg9DWFMBQh2DFU0DxHZ2g==}
engines: {node: ^14.13.1 || >= 16}
peerDependencies:
diff-match-patch: ^1.0.5
svelte: ^3.44.0
vite: ^2.7.0
peerDependenciesMeta:
diff-match-patch:
optional: true
dependencies:
'@rollup/pluginutils': 4.1.2
debug: 4.3.3
kleur: 4.1.4
magic-string: 0.25.7
require-relative: 0.8.7
svelte: 3.44.3
svelte-hmr: 0.14.7_svelte@3.44.3
vite: 2.7.4
transitivePeerDependencies:
- supports-color
dev: true
/@types/node/17.0.1:
resolution: {integrity: sha512-NXKvBVUzIbs6ylBwmOwHFkZS2EXCcjnqr8ZCRNaXBkHAf+3mn/rPcJxwrzuc6movh8fxQAsUUfYklJ/EG+hZqQ==}
dev: true
/@types/pug/2.0.5:
resolution: {integrity: sha512-LOnASQoeNZMkzexRuyqcBBDZ6rS+rQxUMkmj5A0PkhhiSZivLIuz6Hxyr1mkGoEZEkk66faROmpMi4fFkrKsBA==}
dev: true
/@types/sass/1.43.1:
resolution: {integrity: sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==}
dependencies:
'@types/node': 17.0.1
dev: true
/balanced-match/1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
dev: true
/brace-expansion/1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
dev: true
/buffer-crc32/0.2.13:
resolution: {integrity: sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=}
dev: true
/cheap-watch/1.0.4:
resolution: {integrity: sha512-QR/9FrtRL5fjfUJBhAKCdi0lSRQ3rVRRum3GF9wDKp2TJbEIMGhUEr2yU8lORzm9Isdjx7/k9S0DFDx+z5VGtw==}
engines: {node: '>=8'}
dev: true
/concat-map/0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
dev: true
/copy-to-clipboard/3.3.1:
resolution: {integrity: sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw==}
dependencies:
toggle-selection: 1.0.6
dev: false
/debug/4.3.3:
resolution: {integrity: sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
dependencies:
ms: 2.1.2
dev: true
/detect-indent/6.1.0:
resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==}
engines: {node: '>=8'}
dev: true
/es6-promise/3.3.1:
resolution: {integrity: sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=}
dev: true
/esbuild-android-arm64/0.13.15:
resolution: {integrity: sha512-m602nft/XXeO8YQPUDVoHfjyRVPdPgjyyXOxZ44MK/agewFFkPa8tUo6lAzSWh5Ui5PB4KR9UIFTSBKh/RrCmg==}
cpu: [arm64]
os: [android]
requiresBuild: true
dev: true
optional: true
/esbuild-darwin-64/0.13.15:
resolution: {integrity: sha512-ihOQRGs2yyp7t5bArCwnvn2Atr6X4axqPpEdCFPVp7iUj4cVSdisgvEKdNR7yH3JDjW6aQDw40iQFoTqejqxvQ==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/esbuild-darwin-arm64/0.13.15:
resolution: {integrity: sha512-i1FZssTVxUqNlJ6cBTj5YQj4imWy3m49RZRnHhLpefFIh0To05ow9DTrXROTE1urGTQCloFUXTX8QfGJy1P8dQ==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
/esbuild-freebsd-64/0.13.15:
resolution: {integrity: sha512-G3dLBXUI6lC6Z09/x+WtXBXbOYQZ0E8TDBqvn7aMaOCzryJs8LyVXKY4CPnHFXZAbSwkCbqiPuSQ1+HhrNk7EA==}
cpu: [x64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/esbuild-freebsd-arm64/0.13.15:
resolution: {integrity: sha512-KJx0fzEDf1uhNOZQStV4ujg30WlnwqUASaGSFPhznLM/bbheu9HhqZ6mJJZM32lkyfGJikw0jg7v3S0oAvtvQQ==}
cpu: [arm64]
os: [freebsd]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-32/0.13.15:
resolution: {integrity: sha512-ZvTBPk0YWCLMCXiFmD5EUtB30zIPvC5Itxz0mdTu/xZBbbHJftQgLWY49wEPSn2T/TxahYCRDWun5smRa0Tu+g==}
cpu: [ia32]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-64/0.13.15:
resolution: {integrity: sha512-eCKzkNSLywNeQTRBxJRQ0jxRCl2YWdMB3+PkWFo2BBQYC5mISLIVIjThNtn6HUNqua1pnvgP5xX0nHbZbPj5oA==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-arm/0.13.15:
resolution: {integrity: sha512-wUHttDi/ol0tD8ZgUMDH8Ef7IbDX+/UsWJOXaAyTdkT7Yy9ZBqPg8bgB/Dn3CZ9SBpNieozrPRHm0BGww7W/jA==}
cpu: [arm]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-arm64/0.13.15:
resolution: {integrity: sha512-bYpuUlN6qYU9slzr/ltyLTR9YTBS7qUDymO8SV7kjeNext61OdmqFAzuVZom+OLW1HPHseBfJ/JfdSlx8oTUoA==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-mips64le/0.13.15:
resolution: {integrity: sha512-KlVjIG828uFPyJkO/8gKwy9RbXhCEUeFsCGOJBepUlpa7G8/SeZgncUEz/tOOUJTcWMTmFMtdd3GElGyAtbSWg==}
cpu: [mips64el]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-linux-ppc64le/0.13.15:
resolution: {integrity: sha512-h6gYF+OsaqEuBjeesTBtUPw0bmiDu7eAeuc2OEH9S6mV9/jPhPdhOWzdeshb0BskRZxPhxPOjqZ+/OqLcxQwEQ==}
cpu: [ppc64]
os: [linux]
requiresBuild: true
dev: true
optional: true
/esbuild-netbsd-64/0.13.15:
resolution: {integrity: sha512-3+yE9emwoevLMyvu+iR3rsa+Xwhie7ZEHMGDQ6dkqP/ndFzRHkobHUKTe+NCApSqG5ce2z4rFu+NX/UHnxlh3w==}
cpu: [x64]
os: [netbsd]
requiresBuild: true
dev: true
optional: true
/esbuild-openbsd-64/0.13.15:
resolution: {integrity: sha512-wTfvtwYJYAFL1fSs8yHIdf5GEE4NkbtbXtjLWjM3Cw8mmQKqsg8kTiqJ9NJQe5NX/5Qlo7Xd9r1yKMMkHllp5g==}
cpu: [x64]
os: [openbsd]
requiresBuild: true
dev: true
optional: true
/esbuild-sunos-64/0.13.15:
resolution: {integrity: sha512-lbivT9Bx3t1iWWrSnGyBP9ODriEvWDRiweAs69vI+miJoeKwHWOComSRukttbuzjZ8r1q0mQJ8Z7yUsDJ3hKdw==}
cpu: [x64]
os: [sunos]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-32/0.13.15:
resolution: {integrity: sha512-fDMEf2g3SsJ599MBr50cY5ve5lP1wyVwTe6aLJsM01KtxyKkB4UT+fc5MXQFn3RLrAIAZOG+tHC+yXObpSn7Nw==}
cpu: [ia32]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-64/0.13.15:
resolution: {integrity: sha512-9aMsPRGDWCd3bGjUIKG/ZOJPKsiztlxl/Q3C1XDswO6eNX/Jtwu4M+jb6YDH9hRSUflQWX0XKAfWzgy5Wk54JQ==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild-windows-arm64/0.13.15:
resolution: {integrity: sha512-zzvyCVVpbwQQATaf3IG8mu1IwGEiDxKkYUdA4FpoCHi1KtPa13jeScYDjlW0Qh+ebWzpKfR2ZwvqAQkSWNcKjA==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
/esbuild/0.13.15:
resolution: {integrity: sha512-raCxt02HBKv8RJxE8vkTSCXGIyKHdEdGfUmiYb8wnabnaEmHzyW7DCHb5tEN0xU8ryqg5xw54mcwnYkC4x3AIw==}
hasBin: true
requiresBuild: true
optionalDependencies:
esbuild-android-arm64: 0.13.15
esbuild-darwin-64: 0.13.15
esbuild-darwin-arm64: 0.13.15
esbuild-freebsd-64: 0.13.15
esbuild-freebsd-arm64: 0.13.15
esbuild-linux-32: 0.13.15
esbuild-linux-64: 0.13.15
esbuild-linux-arm: 0.13.15
esbuild-linux-arm64: 0.13.15
esbuild-linux-mips64le: 0.13.15
esbuild-linux-ppc64le: 0.13.15
esbuild-netbsd-64: 0.13.15
esbuild-openbsd-64: 0.13.15
esbuild-sunos-64: 0.13.15
esbuild-windows-32: 0.13.15
esbuild-windows-64: 0.13.15
esbuild-windows-arm64: 0.13.15
dev: true
/estree-walker/2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
dev: true
/file-saver/2.0.5:
resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==}
dev: false
/fs.realpath/1.0.0:
resolution: {integrity: sha1-FQStJSMVjKpA20onh8sBQRmU6k8=}
dev: true
/fsevents/2.3.2:
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
requiresBuild: true
dev: true
optional: true
/function-bind/1.1.1:
resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==}
dev: true
/glob/7.2.0:
resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==}
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.0.4
once: 1.4.0
path-is-absolute: 1.0.1
dev: true
/graceful-fs/4.2.8:
resolution: {integrity: sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==}
dev: true
/has/1.0.3:
resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==}
engines: {node: '>= 0.4.0'}
dependencies:
function-bind: 1.1.1
dev: true
/inflight/1.0.6:
resolution: {integrity: sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=}
dependencies:
once: 1.4.0
wrappy: 1.0.2
dev: true
/inherits/2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
dev: true
/is-core-module/2.8.0:
resolution: {integrity: sha512-vd15qHsaqrRL7dtH6QNuy0ndJmRDrS9HAM1CAiSifNUFv4x1a0CCVsj18hJ1mShxIG6T2i1sO78MkP56r0nYRw==}
dependencies:
has: 1.0.3
dev: true
/kleur/4.1.4:
resolution: {integrity: sha512-8QADVssbrFjivHWQU7KkMgptGTl6WAcSdlbBPY4uNF+mWr6DGcKrvY2w4FQJoXch7+fKMjj0dRrL75vk3k23OA==}
engines: {node: '>=6'}
dev: true
/magic-string/0.25.7:
resolution: {integrity: sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA==}
dependencies:
sourcemap-codec: 1.4.8
dev: true
/min-indent/1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
dev: true
/minimatch/3.0.4:
resolution: {integrity: sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==}
dependencies:
brace-expansion: 1.1.11
dev: true
/minimist/1.2.5:
resolution: {integrity: sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==}
dev: true
/mkdirp/0.5.5:
resolution: {integrity: sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==}
hasBin: true
dependencies:
minimist: 1.2.5
dev: true
/mri/1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
engines: {node: '>=4'}
dev: true
/ms/2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
dev: true
/nanoid/3.1.30:
resolution: {integrity: sha512-zJpuPDwOv8D2zq2WRoMe1HsfZthVewpel9CAvTfc/2mBD1uUT/agc5f7GHGWXlYkFvi1mVxe4IjvP2HNrop7nQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/once/1.4.0:
resolution: {integrity: sha1-WDsap3WWHUsROsF9nFC6753Xa9E=}
dependencies:
wrappy: 1.0.2
dev: true
/path-is-absolute/1.0.1:
resolution: {integrity: sha1-F0uSaHNVNP+8es5r9TpanhtcX18=}
engines: {node: '>=0.10.0'}
dev: true
/path-parse/1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
dev: true
/picocolors/1.0.0:
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
dev: true
/picomatch/2.3.0:
resolution: {integrity: sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==}
engines: {node: '>=8.6'}
dev: true
/postcss/8.4.5:
resolution: {integrity: sha512-jBDboWM8qpaqwkMwItqTQTiFikhs/67OYVvblFFTM7MrZjt6yMKd6r2kgXizEbTTljacm4NldIlZnhbjr84QYg==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.1.30
picocolors: 1.0.0
source-map-js: 1.0.1
dev: true
/pretty-bytes/5.6.0:
resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==}
engines: {node: '>=6'}
dev: false
/require-relative/0.8.7:
resolution: {integrity: sha1-eZlTn8ngR6N5KPoZb44VY9q9Nt4=}
dev: true
/resolve/1.20.0:
resolution: {integrity: sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==}
dependencies:
is-core-module: 2.8.0
path-parse: 1.0.7
dev: true
/rimraf/2.7.1:
resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==}
hasBin: true
dependencies:
glob: 7.2.0
dev: true
/rollup/2.61.1:
resolution: {integrity: sha512-BbTXlEvB8d+XFbK/7E5doIcRtxWPRiqr0eb5vQ0+2paMM04Ye4PZY5nHOQef2ix24l/L0SpLd5hwcH15QHPdvA==}
engines: {node: '>=10.0.0'}
hasBin: true
optionalDependencies:
fsevents: 2.3.2
dev: true
/sade/1.7.4:
resolution: {integrity: sha512-y5yauMD93rX840MwUJr7C1ysLFBgMspsdTo4UVrDg3fXDvtwOyIqykhVAAm6fk/3au77773itJStObgK+LKaiA==}
engines: {node: '>= 6'}
dependencies:
mri: 1.2.0
dev: true
/sander/0.5.1:
resolution: {integrity: sha1-dB4kXiMfB8r7b98PEzrfohalAq0=}
dependencies:
es6-promise: 3.3.1
graceful-fs: 4.2.8
mkdirp: 0.5.5
rimraf: 2.7.1
dev: true
/sorcery/0.10.0:
resolution: {integrity: sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=}
hasBin: true
dependencies:
buffer-crc32: 0.2.13
minimist: 1.2.5
sander: 0.5.1
sourcemap-codec: 1.4.8
dev: true
/source-map-js/1.0.1:
resolution: {integrity: sha512-4+TN2b3tqOCd/kaGRJ/sTYA0tR0mdXx26ipdolxcwtJVqEnqNYvlCAt1q3ypy4QMlYus+Zh34RNtYLoq2oQ4IA==}
engines: {node: '>=0.10.0'}
dev: true
/sourcemap-codec/1.4.8:
resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==}
dev: true
/strip-indent/3.0.0:
resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==}
engines: {node: '>=8'}
dependencies:
min-indent: 1.0.1
dev: true
/svelte-hmr/0.14.7_svelte@3.44.3:
resolution: {integrity: sha512-pDrzgcWSoMaK6AJkBWkmgIsecW0GChxYZSZieIYfCP0v2oPyx2CYU/zm7TBIcjLVUPP714WxmViE9Thht4etog==}
peerDependencies:
svelte: '>=3.19.0'
dependencies:
svelte: 3.44.3
dev: true
/svelte-preprocess/4.10.1_svelte@3.44.3+typescript@4.5.4:
resolution: {integrity: sha512-NSNloaylf+o9UeyUR2KvpdxrAyMdHl3U7rMnoP06/sG0iwJvlUM4TpMno13RaNqovh4AAoGsx1jeYcIyuGUXMw==}
engines: {node: '>= 9.11.2'}
requiresBuild: true
peerDependencies:
'@babel/core': ^7.10.2
coffeescript: ^2.5.1
less: ^3.11.3
node-sass: '*'
postcss: ^7 || ^8
postcss-load-config: ^2.1.0 || ^3.0.0
pug: ^3.0.0
sass: ^1.26.8
stylus: ^0.54.7
sugarss: ^2.0.0
svelte: ^3.23.0
typescript: ^4.5.2
peerDependenciesMeta:
'@babel/core':
optional: true
coffeescript:
optional: true
less:
optional: true
node-sass:
optional: true
postcss:
optional: true
postcss-load-config:
optional: true
pug:
optional: true
sass:
optional: true
stylus:
optional: true
sugarss:
optional: true
typescript:
optional: true
dependencies:
'@types/pug': 2.0.5
'@types/sass': 1.43.1
detect-indent: 6.1.0
magic-string: 0.25.7
sorcery: 0.10.0
strip-indent: 3.0.0
svelte: 3.44.3
typescript: 4.5.4
dev: true
/svelte/3.44.3:
resolution: {integrity: sha512-aGgrNCip5PQFNfq9e9tmm7EYxWLVHoFsEsmKrtOeRD8dmoGDdyTQ+21xd7qgFd8MNdKGSYvg7F9dr+Tc0yDymg==}
engines: {node: '>= 8'}
dev: true
/toggle-selection/1.0.6:
resolution: {integrity: sha1-bkWxJj8gF/oKzH2J14sVuL932jI=}
dev: false
/tslib/2.3.1:
resolution: {integrity: sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==}
dev: true
/typescript/4.5.4:
resolution: {integrity: sha512-VgYs2A2QIRuGphtzFV7aQJduJ2gyfTljngLzjpfW9FoYZF6xuw1W0vW9ghCKLfcWrCFxK81CSGRAvS1pn4fIUg==}
engines: {node: '>=4.2.0'}
hasBin: true
dev: true
/vite/2.7.4:
resolution: {integrity: sha512-f+0426k9R/roz5mRNwJlQ+6UOnhCwIypJSbfgCmsVzVJe9jTTM5iRX2GWYUean+iqPBWaU/dYLryx9AoH2pmrw==}
engines: {node: '>=12.2.0'}
hasBin: true
peerDependencies:
less: '*'
sass: '*'
stylus: '*'
peerDependenciesMeta:
less:
optional: true
sass:
optional: true
stylus:
optional: true
dependencies:
esbuild: 0.13.15
postcss: 8.4.5
resolve: 1.20.0
rollup: 2.61.1
optionalDependencies:
fsevents: 2.3.2
dev: true
/wrappy/1.0.2:
resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=}
dev: true

View File

@ -1,3 +0,0 @@
/// <reference types="@sveltejs/kit" />
/// <reference types="svelte" />
/// <reference types="vite/client" />

View File

@ -1,74 +0,0 @@
export type NoteMeta = { type: 'text' | 'file' }
export type Note = {
contents: string
meta: NoteMeta
views?: number
expiration?: number
}
export type NoteInfo = {}
export type NotePublic = Pick<Note, 'contents' | 'meta'>
export type NoteCreate = Omit<Note, 'meta'> & { meta: string }
export type FileDTO = Pick<File, 'name' | 'size' | 'type'> & {
contents: string
}
type CallOptions = {
url: string
method: string
body?: any
}
export class PayloadToLargeError extends Error {}
export async function call(options: CallOptions) {
const response = await fetch('/api/' + options.url, {
method: options.method,
body: options.body === undefined ? undefined : JSON.stringify(options.body),
mode: 'cors',
headers: {
'Content-Type': 'application/json',
},
})
if (!response.ok) {
if (response.status === 413) throw new PayloadToLargeError()
else throw new Error('API call failed')
}
return response.json()
}
export async function create(note: Note) {
const { meta, ...rest } = note
const body: NoteCreate = {
...rest,
meta: JSON.stringify(meta),
}
const data = await call({
url: 'notes/',
method: 'post',
body,
})
return data as { id: string }
}
export async function get(id: string): Promise<NotePublic> {
const data = await call({
url: `notes/${id}`,
method: 'delete',
})
const { contents, meta } = data
return {
contents,
meta: JSON.parse(meta) as NoteMeta,
}
}
export async function info(id: string): Promise<NoteInfo> {
const data = await call({
url: `notes/${id}`,
method: 'get',
})
return data
}

View File

@ -1,71 +0,0 @@
export class Hex {
static encode(buffer: ArrayBuffer): string {
let s = ''
for (const i of new Uint8Array(buffer)) {
s += i.toString(16).padStart(2, '0')
}
return s
}
static decode(s: string): ArrayBuffer {
const size = s.length / 2
const buffer = new Uint8Array(size)
for (let i = 0; i < size; i++) {
const idx = i * 2
const segment = s.slice(idx, idx + 2)
buffer[i] = parseInt(segment, 16)
}
return buffer
}
}
const ALG = 'AES-GCM'
export function getRandomBytes(size = 16): Uint8Array {
return window.crypto.getRandomValues(new Uint8Array(size))
}
export function getKeyFromString(password: string) {
return window.crypto.subtle.importKey(
'raw',
new TextEncoder().encode(password),
'PBKDF2',
false,
['deriveBits', 'deriveKey']
)
}
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']
)
}
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(':')
}
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)
}

View File

@ -1,13 +0,0 @@
export class Files {
static toString(f: File | Blob): Promise<string> {
const reader = new window.FileReader()
reader.readAsDataURL(f)
return new Promise((resolve) => {
reader.onloadend = () => resolve(reader.result as string)
})
}
static fromString(s: string): Promise<Blob> {
return fetch(s).then((r) => r.blob())
}
}

View File

@ -1,17 +0,0 @@
import { call } from '$lib/api'
import { writable } from 'svelte/store'
export type Status = {
version: string
max_size: number
}
export const status = writable<null | Status>(null)
export async function init() {
const data = await call({
url: 'status',
method: 'get',
})
status.set(data)
}

View File

@ -1,72 +0,0 @@
<script lang="ts">
import type { FileDTO } from '$lib/api'
import { Files } from '$lib/files'
import { createEventDispatcher } from 'svelte'
import MaxSize from './MaxSize.svelte'
export let label: string = ''
let files: File[] = []
const dispatch = createEventDispatcher<{ file: string }>()
async function onInput(e: Event) {
const input = e.target as HTMLInputElement
if (input.files.length) {
files = Array.from(input.files)
const data: FileDTO[] = await Promise.all(
files.map(async (file) => ({
name: file.name,
type: file.type,
size: file.size,
contents: await Files.toString(file),
}))
)
console.debug(
'files',
data.map((d) => d.contents.length)
)
dispatch('file', JSON.stringify(data))
} else {
dispatch('file', '')
}
}
</script>
<label>
<small>
{label}
</small>
<input type="file" on:change={onInput} multiple />
<div class="box">
{#if files.length}
<div>
<b>Selected Files</b>
{#each files as file}
<div class="file">
{file.name}
</div>
{/each}
</div>
{:else}
<div>
<b>No Files Selected</b>
<br />
<small>max: <MaxSize /></small>
</div>
{/if}
</div>
</label>
<style>
input {
display: none;
}
.box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
}
</style>

View File

@ -1,34 +0,0 @@
<script lang="ts">
import { onMount } from 'svelte'
export let icon: string = ''
export let href: string = ''
$: src = href || `/icons/${icon}.svg`
let html = null
onMount(async () => {
html = await fetch(src).then((res) => res.text())
})
</script>
{#if html === null}
<img on:click {...$$restProps} {src} alt={icon} />
{:else}
<div on:click {...$$restProps}>
{@html html}
</div>
{/if}
<style>
img,
div {
display: inline-block;
contain: strict;
box-sizing: content-box;
}
div > :global(svg) {
display: block;
fill: currentColor;
}
</style>

View File

@ -1,12 +0,0 @@
<script lang="ts">
import { status } from '$lib/stores/status'
import prettyBytes from 'pretty-bytes'
</script>
<span>
{#if $status !== null}
{prettyBytes($status.max_size, { binary: true })}
{:else}
loading...
{/if}
</span>

View File

@ -1,69 +0,0 @@
<script lang="ts">
import type { FileDTO, NotePublic } from '$lib/api'
import { Files } from '$lib/files'
import copy from 'copy-to-clipboard'
import { saveAs } from 'file-saver'
import prettyBytes from 'pretty-bytes'
import Button from './Button.svelte'
export let note: NotePublic
let files: FileDTO[] = []
$: if (note.meta.type === 'file') {
files = JSON.parse(note.contents) as FileDTO[]
}
$: download = () => {
for (const file of files) {
downloadFile(file)
}
}
async function downloadFile(file: FileDTO) {
const f = new File([await Files.fromString(file.contents)], file.name, {
type: file.type,
})
saveAs(f)
}
</script>
<p class="error-text">you will <b>not</b> get the chance to see the note again.</p>
{#if note.meta.type === 'text'}
<div class="note" data-testid="note-result">
{note.contents}
</div>
<Button on:click={() => copy(note.contents)}>copy to clipboard</Button>
{:else}
{#each files as file}
<div class="note file" data-testid="note-result">
<b on:click={() => downloadFile(file)}> {file.name}</b>
<small> {file.type} {prettyBytes(file.size)}</small>
</div>
{/each}
<Button on:click={download}>download all</Button>
{/if}
<style>
.note {
width: 100%;
margin: 0;
padding: 0;
border: 2px solid var(--ui-bg-1);
outline: none;
padding: 0.5rem;
white-space: pre;
overflow: auto;
margin-bottom: 0.5rem;
}
.note b {
cursor: pointer;
}
.note.file {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@ -1,195 +0,0 @@
<script lang="ts">
import { create, Note, PayloadToLargeError } from '$lib/api'
import { encrypt, getKeyFromString, getRandomBytes, Hex } from '$lib/crypto'
import Button from '$lib/ui/Button.svelte'
import FileUpload from '$lib/ui/FileUpload.svelte'
import MaxSize from '$lib/ui/MaxSize.svelte'
import Switch from '$lib/ui/Switch.svelte'
import TextArea from '$lib/ui/TextArea.svelte'
import TextInput from '$lib/ui/TextInput.svelte'
import { blur } from 'svelte/transition'
let note: Note = {
contents: '',
meta: { type: 'text' },
views: 1,
expiration: 60,
}
let result: { password: string; id: string } | null = null
let advanced = false
let file = false
let type = false
let message = ''
let loading = false
let error: string | null = null
$: if (!advanced) {
note.views = 1
type = false
}
$: {
let fraction: string
fraction = type ? `${note.expiration} minutes` : `${note.views} views`
message = 'the note will expire and be destroyed after ' + fraction
}
$: note.meta.type = file ? 'file' : 'text'
$: if (!file) {
note.contents = ''
}
async function submit() {
try {
error = null
loading = true
const password = Hex.encode(getRandomBytes(32))
const key = await getKeyFromString(password)
const data: Note = {
contents: await encrypt(note.contents, key),
meta: note.meta,
}
// @ts-ignore
if (type) data.expiration = parseInt(note.expiration)
// @ts-ignore
else data.views = parseInt(note.views)
const response = await create(data)
result = {
password: password,
id: response.id,
}
} catch (e) {
if (e instanceof PayloadToLargeError) {
error = 'could not create not. note is to big'
} else {
error = 'could not create note. please try again.'
}
} finally {
loading = false
}
}
function reset() {
window.location.reload()
}
</script>
{#if result}
<TextInput
type="text"
readonly
label="share link"
value="{window.location.origin}/note/{result.id}#{result.password}"
copy
data-testid="note-share-link"
/>
<br />
<p>
<b>availability:</b>
<br />
the note is not guaranteed to be stored as everything is kept in ram, if it fills up the oldest notes
will be removed.
<br />
(you probably will be fine, just be warned.)
</p>
<br />
<Button on:click={reset}>new note</Button>
{:else}
<p>
Easily send <i>fully encrypted</i>, secure notes or files with one click. Just create a note and
share the link.
</p>
<form on:submit|preventDefault={submit}>
<fieldset disabled={loading}>
{#if file}
<FileUpload label="file" on:file={(f) => (note.contents = f.detail)} />
{:else}
<TextArea
label="note"
bind:value={note.contents}
placeholder="..."
data-testid="input-note"
/>
{/if}
<div class="bottom">
<Switch class="file" label="file" bind:value={file} />
<Switch label="advanced" bind:value={advanced} />
<div class="grow" />
<div class="tr">
<small>max: <MaxSize /> </small>
<br />
<Button type="submit" data-testid="button-create">create</Button>
</div>
</div>
{#if error}
<div class="error-text">{error}</div>
{/if}
<p>
<br />
{#if loading}
loading...
{:else}
{message}
{/if}
</p>
{#if advanced}
<div transition:blur={{ duration: 250 }}>
<br />
<div class="fields">
<TextInput
type="number"
label="views"
bind:value={note.views}
disabled={type}
max={100}
/>
<div class="middle-switch">
<Switch label="mode" bind:value={type} color={false} />
</div>
<TextInput
type="number"
label="minutes"
bind:value={note.expiration}
disabled={!type}
max={360}
/>
</div>
</div>
{/if}
</fieldset>
</form>
{/if}
<style>
.bottom {
display: flex;
align-items: flex-end;
margin-top: 0.5rem;
}
.bottom :global(.file) {
margin-right: 0.5rem;
}
.grow {
flex: 1;
}
.middle-switch {
margin: 0 1rem;
}
.error-text {
margin-top: 0.5rem;
}
.fields {
display: flex;
}
</style>

View File

@ -1,107 +0,0 @@
<header>
<a href="/">
<svg
width="100%"
height="100%"
viewBox="0 0 450 200"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xml:space="preserve"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"
><g id="Logo"
><clipPath id="_clip1"><rect x="6.336" y="3.225" width="193.55" height="193.55" /></clipPath
><g clip-path="url(#_clip1)"
><g
><g
><path
d="M173.425,43.296c-2.087,-0 -3.78,1.693 -3.78,3.78c-0,2.087 1.693,3.78 3.78,3.78c2.087,0 3.78,-1.693 3.78,-3.78c0,-2.087 -1.693,-3.78 -3.78,-3.78Z"
style="fill-rule:nonzero;"
/></g
></g
><g
><g
><path
d="M103.112,134.023c-2.087,-0 -3.781,1.693 -3.781,3.78c0,2.087 1.694,3.78 3.781,3.78c2.086,0 3.78,-1.693 3.78,-3.78c-0,-2.087 -1.694,-3.78 -3.78,-3.78Z"
style="fill-rule:nonzero;"
/></g
></g
><g
><g
><path
d="M156.036,96.22c-2.088,-0 -3.781,1.692 -3.781,3.78c0,18.76 -15.262,34.023 -34.022,34.023c-2.088,-0 -3.781,1.692 -3.781,3.78c0,2.088 1.693,3.78 3.781,3.78c22.929,0 41.583,-18.654 41.583,-41.583c-0,-2.088 -1.693,-3.78 -3.78,-3.78Z"
style="fill-rule:nonzero;"
/></g
></g
><g
><g
><path
d="M199.488,60.507l-9.515,-19.026c-4.102,-8.207 -12.35,-13.306 -21.527,-13.306c-7.479,-0 -14.626,3.547 -19.154,9.498l-10.679,12.016c-2.846,-4.047 -7.021,-7.049 -11.83,-8.421l-19.102,-5.459c-14.623,-4.178 -28.92,-15.441 -39.227,-30.901c-0.924,-1.386 -2.646,-2.003 -4.241,-1.521c-1.594,0.483 -2.684,1.953 -2.684,3.618l-0,20.372c-0,9.468 1.417,18.804 4.219,27.813c-2.936,0.73 -5.896,1.34 -8.843,1.816c-5.772,0.936 -11.653,1.411 -17.48,1.411l-29.308,-0c-1.374,-0 -2.64,0.746 -3.307,1.948c-0.666,1.202 -0.627,2.671 0.101,3.836l22.637,36.219c9.672,15.473 26.329,25.183 44.578,25.983l-36.36,41.158c-5.602,5.728 -3.655,15.315 3.746,18.396l20.017,9.887c0.089,0.044 0.179,0.084 0.271,0.121c5.79,2.313 12.389,-0.496 14.726,-6.279l13.969,-32.982l27.738,0c31.966,0 58.972,-25.967 58.972,-56.704l0,-22.682c0,-6.253 5.088,-11.341 11.341,-11.341l7.56,0c1.311,0 2.528,-0.678 3.216,-1.793c0.689,-1.114 0.752,-2.506 0.166,-3.677Zm-130.399,-42.236c10.418,12.203 23.307,21.034 36.515,24.808l19.103,5.459c3.726,1.063 6.866,3.624 8.666,7.048l-10.048,11.307l-17.652,-10.591c-7.646,-4.588 -16.746,-6.412 -25.776,-4.951c-2.838,0.46 -4.649,1.038 -6.877,1.759c-2.609,-8.332 -3.931,-16.971 -3.931,-25.733l0,-9.106Zm119.457,40.146c-10.422,-0 -18.901,8.479 -18.901,18.901l-0,22.682c-0,26.639 -23.544,49.144 -51.412,49.144l-30.243,-0c-10.77,-0 -20.451,5.983 -25.265,15.615l-0.797,1.596c-0.934,1.867 -0.177,4.137 1.691,5.071c1.867,0.934 4.138,0.176 5.071,-1.691c0.44,-0.586 3.306,-9.102 13.21,-12.125l-12.349,29.159c-0.01,0.024 -0.02,0.047 -0.029,0.071c-0.75,1.877 -2.864,2.851 -4.8,2.148c-21.279,-10.506 -19.997,-9.888 -20.252,-9.99c-2.526,-1.01 -3.191,-4.259 -1.267,-6.182c0.13,-0.131 8.026,-9.078 41.009,-46.411c13.867,-0.617 26.841,-6.319 36.694,-16.172c1.476,-1.476 1.476,-3.87 0,-5.346c-1.476,-1.477 -3.87,-1.476 -5.346,-0c-16.827,16.828 -36.803,13.634 -39.028,14.014c-16.603,0 -31.771,-8.407 -40.573,-22.488l-2.417,-3.868l2.729,1.065c17.307,6.753 38.919,4.347 53.816,-5.586c1.737,-1.158 2.207,-3.505 1.049,-5.242c-1.159,-1.737 -3.505,-2.207 -5.243,-1.048c-13.085,8.724 -31.922,10.666 -46.875,4.832l-12.185,-4.753l-9.896,-15.836l22.488,0c6.231,0 12.519,-0.507 18.689,-1.507c12.711,-2.055 18.051,-4.855 22.992,-5.655c7.182,-1.163 14.516,0.274 20.678,3.97l29.625,17.775c1.789,1.074 4.112,0.494 5.186,-1.296c1.075,-1.79 0.495,-4.113 -1.295,-5.187l-5.378,-3.226c26.56,-29.893 25.14,-28.272 25.32,-28.511c3.101,-4.136 8.038,-6.605 13.204,-6.605c6.294,0 11.951,3.497 14.764,9.127l6.779,13.555l-1.443,-0Z"
style="fill-rule:nonzero;"
/></g
></g
></g
><g
><path
d="M222.664,114.692c0.85,-0 1.274,0.586 1.274,1.759c0,1.174 -0.242,2.347 -0.728,3.52c-0.485,1.173 -1.183,2.326 -2.093,3.459c-0.91,1.132 -2.185,2.063 -3.823,2.791c-1.639,0.728 -3.489,1.092 -5.552,1.092c-4.531,0 -8.081,-1.143 -10.65,-3.428c-2.569,-2.286 -3.853,-5.644 -3.853,-10.073c-0,-4.43 1.133,-7.889 3.398,-10.377c2.265,-2.488 5.158,-3.732 8.677,-3.732c2.549,0 4.41,0.678 5.583,2.033c1.173,1.355 1.76,2.964 1.76,4.824c-0,1.012 -0.304,1.821 -0.91,2.428c-0.607,0.606 -1.194,0.91 -1.76,0.91c-1.052,-0 -1.74,-0.243 -2.063,-0.728c0.242,-0.607 0.364,-1.477 0.364,-2.61c-0,-1.132 -0.385,-2.164 -1.153,-3.094c-0.769,-0.931 -1.699,-1.396 -2.792,-1.396c-1.901,0 -3.266,0.93 -4.095,2.791c-0.83,1.861 -1.244,4.784 -1.244,8.769c-0,3.984 0.778,6.867 2.336,8.647c1.557,1.78 3.732,2.67 6.523,2.67c2.791,-0 5.138,-0.86 7.039,-2.579c1.901,-1.72 2.852,-4.157 2.852,-7.312c0.04,-0.243 0.344,-0.364 0.91,-0.364Z"
style="fill-rule:nonzero;"
/><path
d="M227.337,116.815l-0,9.649c-0.405,0.566 -1.113,0.849 -2.124,0.849c-1.012,0 -1.821,-0.313 -2.428,-0.94c-0.606,-0.627 -0.91,-1.548 -0.91,-2.761l0,-23.059c0.405,-0.566 1.113,-0.85 2.124,-0.85c1.011,0 1.821,0.314 2.427,0.941c0.607,0.627 0.911,1.547 0.911,2.761l-0,4.612c2.022,-5.017 4.814,-7.525 8.373,-7.525c2.549,0 4.41,0.678 5.583,2.033c1.173,1.355 1.76,2.963 1.76,4.824c-0,1.012 -0.304,1.821 -0.91,2.427c-0.607,0.607 -1.194,0.911 -1.76,0.911c-1.052,-0 -1.74,-0.243 -2.063,-0.728c0.242,-0.607 0.364,-1.477 0.364,-2.61c-0,-1.132 -0.384,-2.164 -1.153,-3.094c-0.769,-0.931 -1.699,-1.396 -2.791,-1.396c-1.902,-0 -3.611,1.729 -5.128,5.188c-1.517,3.459 -2.275,6.382 -2.275,8.768Z"
style="fill-rule:nonzero;"
/><path
d="M245.419,103.405c0,-1.214 0.304,-2.134 0.911,-2.761c0.606,-0.627 1.375,-0.941 2.306,-0.941c0.93,0 1.678,0.284 2.245,0.85l-0,17.415c-0,4.814 1.213,7.221 3.641,7.221c2.184,0 3.974,-1.153 5.37,-3.458c1.395,-2.306 2.093,-6.19 2.093,-11.651l0,-6.675c0,-1.214 0.304,-2.134 0.91,-2.761c0.607,-0.627 1.376,-0.941 2.306,-0.941c0.931,0 1.679,0.284 2.246,0.85l-0,30.826c0.485,-0.041 1.254,-0.061 2.305,-0.061c1.052,0 1.578,0.445 1.578,1.335l-0.364,0.91c-1.537,0 -2.71,0.021 -3.519,0.061l-0,3.034c-0,4.167 -0.941,7.454 -2.822,9.861c-1.881,2.407 -4.399,3.61 -7.555,3.61c-1.861,0 -3.509,-0.556 -4.945,-1.669c-1.436,-1.112 -2.155,-2.862 -2.155,-5.248c0,-3.075 1.093,-5.522 3.277,-7.343c2.185,-1.82 5.097,-3.095 8.738,-3.823l0,-9.466c-1.699,3.156 -4.429,4.733 -8.192,4.733c-2.589,0 -4.632,-0.829 -6.128,-2.488c-1.497,-1.658 -2.246,-4.045 -2.246,-7.16l0,-14.26Zm7.646,39.989c0,1.416 0.385,2.498 1.153,3.246c0.769,0.748 1.699,1.123 2.791,1.123c1.093,-0 1.983,-0.324 2.67,-0.971c1.538,-1.376 2.306,-5.522 2.306,-12.44c-5.946,1.335 -8.92,4.349 -8.92,9.042Z"
style="fill-rule:nonzero;"
/><path
d="M298.576,104.861c1.335,2.306 2.002,5.411 2.002,9.315c0,3.904 -0.981,7.059 -2.943,9.466c-1.962,2.407 -4.52,3.611 -7.676,3.611c-1.861,-0 -3.509,-0.648 -4.945,-1.942c-1.436,-1.295 -2.155,-3.358 -2.155,-6.19c0,-6.634 3.297,-11.59 9.891,-14.866c-1.051,-1.457 -2.518,-2.185 -4.399,-2.185c-1.881,0 -3.671,0.93 -5.37,2.791c-1.699,1.861 -2.549,4.653 -2.549,8.374l0,36.105c-0.404,0.567 -1.112,0.85 -2.124,0.85c-1.011,-0 -1.82,-0.314 -2.427,-0.941c-0.607,-0.627 -0.91,-1.547 -0.91,-2.761l-0,-45.935c0.405,-0.566 1.112,-0.85 2.124,-0.85c1.011,0 1.82,0.314 2.427,0.941c0.607,0.627 0.91,1.547 0.91,2.761l0,1.335c2.104,-3.358 4.885,-5.037 8.344,-5.037c3.459,0 6.159,0.971 8.101,2.913c4.247,-1.375 9.001,-2.063 14.26,-2.063c1.092,-0 1.638,0.445 1.638,1.335l-0.364,0.91c-4.895,0 -9.507,0.688 -13.835,2.063Zm-8.738,20.025c3.317,-0 4.976,-3.297 4.976,-9.891c-0,-3.479 -0.284,-6.21 -0.85,-8.192c-5.34,2.913 -8.01,7.08 -8.01,12.5c0,1.78 0.385,3.156 1.153,4.127c0.769,0.971 1.679,1.456 2.731,1.456Z"
style="fill-rule:nonzero;"
/><path
d="M308.285,94.424c-0,-1.213 0.303,-2.134 0.91,-2.761c0.607,-0.627 1.375,-0.94 2.306,-0.94c0.93,-0 1.679,0.283 2.245,0.849l0,8.981l8.313,-0c0.85,-0 1.406,0.162 1.669,0.485c0.263,0.324 0.394,0.87 0.394,1.639l-10.376,-0l0,15.291c0,2.225 0.637,3.945 1.911,5.158c1.275,1.214 2.741,1.821 4.4,1.821c2.508,-0 4.349,-0.931 5.522,-2.792c1.173,-1.861 1.76,-4.874 1.76,-9.041c0.08,-0.243 0.384,-0.364 0.91,-0.364c0.728,-0 1.092,1.072 1.092,3.216c-0,3.277 -0.819,5.987 -2.458,8.131c-1.638,2.144 -4.156,3.216 -7.554,3.216c-3.398,0 -6.089,-0.89 -8.071,-2.67c-1.982,-1.78 -2.973,-4.429 -2.973,-7.949l-0,-22.27Z"
style="fill-rule:nonzero;"
/><path
d="M345.543,101.463c0.526,-1.173 1.365,-1.76 2.518,-1.76c1.153,0 2.013,0.284 2.579,0.85l-0,30.826c0.485,-0.041 1.254,-0.061 2.306,-0.061c1.052,0 1.578,0.445 1.578,1.335l-0.365,0.91c-1.537,0 -2.71,0.021 -3.519,0.061l-0,3.155c-0.041,4.167 -1.001,7.434 -2.882,9.8c-1.882,2.367 -4.38,3.55 -7.494,3.55c-1.861,0 -3.51,-0.556 -4.946,-1.669c-1.436,-1.112 -2.154,-2.862 -2.154,-5.248c-0,-3.075 1.092,-5.522 3.277,-7.343c2.184,-1.82 5.097,-3.095 8.738,-3.823l-0,-8.92c-1.699,2.792 -4.046,4.187 -7.039,4.187c-7.08,0 -10.619,-4.632 -10.619,-13.896c-0,-4.692 1.102,-8.151 3.307,-10.376c2.204,-2.225 5.127,-3.338 8.768,-3.338c2.791,0 4.774,0.587 5.947,1.76Zm-0.364,12.986l-0,-4.308c-0,-2.873 -0.486,-4.936 -1.457,-6.19c-0.971,-1.254 -2.093,-1.881 -3.367,-1.881c-1.275,0 -2.276,0.121 -3.004,0.364c-0.728,0.243 -1.436,0.748 -2.124,1.517c-1.295,1.456 -1.942,4.612 -1.942,9.466c0,3.196 0.283,5.684 0.85,7.464c0.566,1.78 1.213,2.903 1.942,3.368c0.728,0.465 1.749,0.698 3.064,0.698c1.315,-0 2.65,-0.84 4.005,-2.519c1.355,-1.678 2.033,-4.338 2.033,-7.979Zm-0,22.209l-0,-2.306c-5.947,1.335 -8.92,4.349 -8.92,9.042c-0,1.416 0.384,2.498 1.153,3.246c0.768,0.748 1.678,1.123 2.73,1.123c1.699,-0 2.963,-0.789 3.793,-2.367c0.829,-1.578 1.244,-4.49 1.244,-8.738Z"
style="fill-rule:nonzero;"
/><path
d="M381.891,113.478c0.849,-0 1.274,0.647 1.274,1.942c-0,2.993 -1.042,5.724 -3.125,8.192c-2.084,2.467 -5.249,3.701 -9.497,3.701c-4.247,0 -7.656,-1.143 -10.224,-3.428c-2.569,-2.286 -3.854,-5.644 -3.854,-10.073c0,-4.43 1.133,-7.889 3.398,-10.377c2.266,-2.488 5.158,-3.732 8.678,-3.732c2.548,0 4.409,0.678 5.582,2.033c1.174,1.355 1.76,2.964 1.76,4.824c0,3.034 -1.264,5.553 -3.792,7.555c-2.529,2.003 -5.755,3.004 -9.679,3.004c0.364,2.589 1.305,4.541 2.822,5.856c1.517,1.314 3.519,1.972 6.007,1.972c2.488,-0 4.733,-0.941 6.736,-2.822c2.002,-1.881 3.003,-4.642 3.003,-8.283c0.041,-0.243 0.344,-0.364 0.911,-0.364Zm-19.661,0.85l-0,0.546c2.508,-0.324 4.682,-1.113 6.523,-2.367c1.841,-1.254 2.761,-3.115 2.761,-5.583c0,-1.577 -0.344,-2.781 -1.031,-3.61c-0.688,-0.829 -1.659,-1.244 -2.913,-1.244c-1.901,0 -3.267,0.93 -4.096,2.791c-0.829,1.861 -1.244,5.017 -1.244,9.467Z"
style="fill-rule:nonzero;"
/><path
d="M393.359,99.703c3.601,0 6.534,1.194 8.799,3.581c2.265,2.386 3.398,5.633 3.398,9.739c0,4.106 -1.062,7.514 -3.186,10.225c-2.124,2.71 -4.965,4.065 -8.525,4.065c-3.56,0 -6.524,-1.224 -8.89,-3.671c-2.367,-2.447 -3.55,-5.846 -3.55,-10.194c0,-4.349 1.102,-7.727 3.307,-10.134c2.205,-2.407 5.087,-3.611 8.647,-3.611Zm0.061,2.367c-1.092,0 -2.033,0.273 -2.822,0.819c-0.789,0.546 -1.183,1.325 -1.183,2.336c-0,2.589 1.011,5.067 3.034,7.434c2.023,2.366 4.43,3.934 7.221,4.703c0.081,-1.538 0.121,-2.852 0.121,-3.945c0,-4.207 -0.445,-7.15 -1.335,-8.829c-0.89,-1.679 -2.568,-2.518 -5.036,-2.518Zm-6.25,11.347c-0,4.127 0.516,7.08 1.547,8.86c1.032,1.78 2.721,2.67 5.067,2.67c1.618,-0 2.862,-0.415 3.732,-1.244c0.87,-0.83 1.466,-2.175 1.79,-4.036c-2.63,-0.566 -5.047,-1.759 -7.251,-3.58c-2.205,-1.82 -3.813,-3.843 -4.825,-6.068c-0.04,0.688 -0.06,1.821 -0.06,3.398Z"
style="fill-rule:nonzero;"
/><path
d="M442.571,112.75c0.729,-0 1.093,1.072 1.093,3.216c-0,3.438 -0.739,6.189 -2.215,8.252c-1.477,2.064 -3.631,3.095 -6.463,3.095c-2.832,0 -5.006,-0.971 -6.523,-2.912c-1.517,-1.942 -2.275,-4.693 -2.275,-8.253l-0,-7.1c-0,-4.814 -1.214,-7.221 -3.641,-7.221c-2.144,0 -3.904,1.093 -5.28,3.277c-1.375,2.185 -2.063,5.846 -2.063,10.983l0,10.377c-0.404,0.566 -1.112,0.849 -2.124,0.849c-1.011,0 -1.82,-0.313 -2.427,-0.94c-0.607,-0.627 -0.91,-1.548 -0.91,-2.761l0,-23.059c0.405,-0.566 1.173,-0.85 2.306,-0.85c2.103,0 3.155,1.517 3.155,4.552c1.699,-3.035 4.39,-4.552 8.071,-4.552c2.629,0 4.723,0.83 6.28,2.488c1.558,1.659 2.337,4.046 2.337,7.161l-0,8.616c-0,2.306 0.364,4.046 1.092,5.219c0.728,1.173 1.81,1.76 3.246,1.76c1.436,-0 2.7,-0.9 3.793,-2.701c1.092,-1.8 1.638,-4.844 1.638,-9.132c0.041,-0.243 0.344,-0.364 0.91,-0.364Z"
style="fill-rule:nonzero;"
/></g
></g
></svg
>
</a>
</header>
<style>
a {
border: none;
}
header {
text-align: center;
margin-top: 4rem;
margin-bottom: 2rem;
}
@media screen and (max-width: 30rem) {
header {
margin-top: 1rem;
margin-bottom: 1rem;
}
header svg {
max-height: 4rem;
}
}
header svg {
width: 100%;
max-width: 16rem;
transform: translateX(-1rem);
fill: currentColor;
}
</style>

View File

@ -1,32 +0,0 @@
<script lang="ts">
import { init } from '$lib/stores/status'
import Footer from '$lib/views/Footer.svelte'
import Header from '$lib/views/Header.svelte'
import { onMount } from 'svelte'
import '../app.css'
onMount(() => {
init()
})
</script>
<svelte:head>
<title>cryptgeon</title>
</svelte:head>
<main>
<Header />
<slot />
</main>
<Footer />
<style>
main {
padding: 1rem;
padding-bottom: 4rem;
width: 100%;
max-width: 35rem;
margin: 0 auto;
}
</style>

View File

@ -1,81 +0,0 @@
<script context="module" lang="ts">
export async function load({ page }) {
return {
props: page.params,
}
}
</script>
<script lang="ts">
import type { NotePublic } from '$lib/api'
import { get, info } from '$lib/api'
import { decrypt, getKeyFromString } from '$lib/crypto'
import Button from '$lib/ui/Button.svelte'
import ShowNote from '$lib/ui/ShowNote.svelte'
import { onMount } from 'svelte'
export let id: string
let password: string
let note: NotePublic | null = null
let exists = false
let loading = true
let error = false
onMount(async () => {
try {
loading = true
error = null
password = window.location.hash.slice(1)
await info(id)
exists = true
} catch {
exists = false
} finally {
loading = false
}
})
async function show() {
try {
error = false
loading = true
const data = note || (await get(id)) // Don't get the content twice on wrong password.
const key = await getKeyFromString(password)
data.contents = await decrypt(data.contents, key)
note = data
} catch {
error = true
} finally {
loading = false
}
}
</script>
{#if !loading}
{#if !exists}
<p class="error-text" data-testid="note-not-found">
note was not found or was already deleted.
</p>
{:else if note && !error}
<ShowNote {note} />
{:else}
<form on:submit|preventDefault={show}>
<fieldset>
<p>click below to show and delete the note if the counter has reached it's limit</p>
<Button type="submit" data-testid="button-show">show note</Button>
{#if error}
<br />
<p class="error-text">
wrong password. could not decipher. probably a broken link. note was destroyed.
<br />
</p>
{/if}
</fieldset>
</form>
{/if}
{/if}
{#if loading}
<p>loading...</p>
{/if}

View File

@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Contrast</title><path d='M256 32C132.29 32 32 132.29 32 256s100.29 224 224 224 224-100.29 224-224S379.71 32 256 32zM128.72 383.28A180 180 0 01256 76v360a178.82 178.82 0 01-127.28-52.72z'/></svg>

Before

Width:  |  Height:  |  Size: 279 B

View File

@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Copy</title><path d='M456 480H136a24 24 0 01-24-24V128a16 16 0 0116-16h328a24 24 0 0124 24v320a24 24 0 01-24 24z'/><path d='M112 80h288V56a24 24 0 00-24-24H60a28 28 0 00-28 28v316a24 24 0 0024 24h24V112a32 32 0 0132-32z'/></svg>

Before

Width:  |  Height:  |  Size: 313 B

View File

@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Dice</title><path d='M48 366.92L240 480V284L48 170zM192 288c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zm-96 32c8.84 0 16 10.75 16 24s-7.16 24-16 24-16-10.75-16-24 7.16-24 16-24zM272 284v196l192-113.08V170zm48 140c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm96 32c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm0-88c-8.84 0-16-10.75-16-24s7.16-24 16-24 16 10.75 16 24-7.16 24-16 24zm32 77.64zM256 32L64 144l192 112 192-112zm0 120c-13.25 0-24-7.16-24-16s10.75-16 24-16 24 7.16 24 16-10.75 16-24 16z'/></svg>

Before

Width:  |  Height:  |  Size: 728 B

View File

@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Eye Off</title><path d='M63.998 86.004l21.998-21.998L448 426.01l-21.998 21.998zM259.34 192.09l60.57 60.57a64.07 64.07 0 00-60.57-60.57zM252.66 319.91l-60.57-60.57a64.07 64.07 0 0060.57 60.57z'/><path d='M256 352a96 96 0 01-92.6-121.34l-69.07-69.08C66.12 187.42 39.24 221.14 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416A233.47 233.47 0 00335 402.2l-53.61-53.6A95.84 95.84 0 01256 352zM256 160a96 96 0 0192.6 121.34L419.26 352c29.15-26.25 56.07-61.56 76.74-96-26.38-43.43-62.9-88.56-101.18-114.82C351.1 111.2 304.31 96 255.76 96a222.92 222.92 0 00-78.21 14.29l53.11 53.11A95.84 95.84 0 01256 160z'/></svg>

Before

Width:  |  Height:  |  Size: 720 B

View File

@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Eye</title><circle cx='256' cy='256' r='64'/><path d='M394.82 141.18C351.1 111.2 304.31 96 255.76 96c-43.69 0-86.28 13-126.59 38.48C88.52 160.23 48.67 207 16 256c26.42 44 62.56 89.24 100.2 115.18C159.38 400.92 206.33 416 255.76 416c49 0 95.85-15.07 139.3-44.79C433.31 345 469.71 299.82 496 256c-26.38-43.43-62.9-88.56-101.18-114.82zM256 352a96 96 0 1196-96 96.11 96.11 0 01-96 96z'/></svg>

Before

Width:  |  Height:  |  Size: 474 B

View File

@ -1 +0,0 @@
<svg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'><title>Lock Closed</title><path d='M420 192h-68v-80a96 96 0 10-192 0v80H92a12 12 0 00-12 12v280a12 12 0 0012 12h328a12 12 0 0012-12V204a12 12 0 00-12-12zm-106 0H198v-80.75a58 58 0 11116 0z'/></svg>

Before

Width:  |  Height:  |  Size: 275 B

View File

@ -1,13 +0,0 @@
import preprocess from 'svelte-preprocess'
import adapter from '@sveltejs/adapter-static'
export default {
preprocess: preprocess(),
kit: {
adapter: adapter({
fallback: 'index.html',
}),
target: '#svelte',
},
}

View File

@ -1,30 +0,0 @@
{
"compilerOptions": {
"moduleResolution": "node",
"module": "es2020",
"lib": ["es2020"],
"target": "es2019",
/**
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
to enforce using \`import type\` instead of \`import\` for Types.
*/
"importsNotUsedAsValues": "error",
"isolatedModules": true,
"resolveJsonModule": true,
/**
To have warnings/errors of the Svelte compiler at the correct position,
enable source maps by default.
*/
"sourceMap": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"baseUrl": ".",
"allowJs": true,
"checkJs": true,
"paths": {
"$lib/*": ["src/lib/*"]
}
},
"include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.ts", "src/**/*.svelte"]
}

23
cryptgeon.code-workspace Normal file
View File

@ -0,0 +1,23 @@
{
"folders": [
{
"path": "."
},
{
"path": "packages/backend"
},
{
"path": "packages/frontend"
},
{
"path": "packages/cli"
},
{
"path": "packages/shared"
}
],
"settings": {
"i18n-ally.localesPaths": ["locales"],
"cSpell.words": ["cryptgeon"]
}
}

View File

@ -1,5 +0,0 @@
{
"fixturesFolder": false,
"pluginsFile": false,
"supportFile": false
}

BIN
design/Github_zh-CN.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

26
docker-compose.dev.yaml Normal file
View File

@ -0,0 +1,26 @@
# DEV Compose file.
# For a production file see: README.md
version: '3.8'
services:
redis:
image: redis:7-alpine
ports:
- 6379:6379
app:
build: .
env_file: .dev.env
depends_on:
- redis
restart: unless-stopped
ports:
- 1234:8000
healthcheck:
test: ['CMD', 'curl', '--fail', 'http://127.0.0.1:8000/api/live/']
interval: 1m
timeout: 3s
retries: 2
start_period: 5s

26
docker-compose.yaml Normal file
View File

@ -0,0 +1,26 @@
version: '3.8'
services:
redis:
image: redis:7-alpine
# Set a size limit. See link below on how to customise.
# https://redis.io/docs/manual/eviction/
# command: redis-server --maxmemory 1gb --maxmemory-policy allkeys-lru
app:
image: cupcakearmy/cryptgeon:latest
depends_on:
- redis
environment:
# Size limit for a single note.
SIZE_LIMIT: 4 MiB
ports:
- 80:8000
# Optional health checks
# healthcheck:
# test: ["CMD", "curl", "--fail", "http://127.0.0.1:8000/api/live/"]
# interval: 1m
# timeout: 3s
# retries: 2
# start_period: 5s

View File

@ -1,21 +0,0 @@
# DEV Compose file.
# For a production file see: README.md
version: '3.7'
services:
memcached:
image: memcached:1-alpine
restart: unless-stopped
entrypoint: memcached -m 128M -I 4M
ports:
- 11211:11211
app:
build: .
depends_on:
- memcached
environment:
SIZE_LIMIT: 4M
ports:
- 80:5000

View File

@ -1,14 +1,13 @@
version: '3.8'
services:
memcached:
image: memcached:1-alpine
entrypoint: memcached -m 128 # Limit to 128 MB Ram, customize at free will.
redis:
image: redis:7-alpine
app:
image: cupcakearmy/cryptgeon:latest
depends_on:
- memcached
- redis
proxy:
image: nginx:alpine

View File

@ -4,7 +4,7 @@ server {
server_name _;
location / {
proxy_pass http://app:5000/;
proxy_pass http://app:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

View File

@ -20,7 +20,7 @@ server {
ssl_trusted_certificate /path/to/fullchain.pem;
location / {
proxy_pass http://app:5000/;
proxy_pass http://app:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

160
examples/scratch/README.md Normal file
View File

@ -0,0 +1,160 @@
# Install from scratch.
This is a tiny guide to install cryptgeon on (probably) any unix system (and maybe windows?) from scratch using traefik as the proxy, which will manage certificates and handle https for us.
1. Install Docker & Docker Compose.
2. Install Traefik.
3. Run the cryptgeon.
4. [Optional] install watchtower to keep up to date.
## Install Docker & Docker Compose
- [Docker](https://docs.docker.com/engine/install/)
- [Compose](https://docs.docker.com/compose/install/)
## Install Traefik 2.0
[Traefik](https://doc.traefik.io/traefik/) is a router & proxy that makes deployment of containers incredibly easy. It will manage all the https certificates, routing, etc.
```sh
/foo/bar/traefik/
├── docker-compose.yaml
└── traefik.yaml
```
```yaml
# docker-compose.yaml
version: '3.8'
services:
traefik:
image: traefik:2.6
restart: unless-stopped
ports:
- '80:80'
- '443:443'
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik.yaml:/etc/traefik/traefik.yaml:ro
- ./data:/data
labels:
- 'traefik.enable=true'
# HTTP to HTTPS redirection
- 'traefik.http.routers.http_catchall.rule=HostRegexp(`{any:.+}`)'
- 'traefik.http.routers.http_catchall.entrypoints=insecure'
- 'traefik.http.routers.http_catchall.middlewares=https_redirect'
- 'traefik.http.middlewares.https_redirect.redirectscheme.scheme=https'
- 'traefik.http.middlewares.https_redirect.redirectscheme.permanent=true'
networks:
default:
external: true
name: proxy
```
```yaml
# traefik.yaml
api:
dashboard: true
# Define HTTP and HTTPS entrypoint
entryPoints:
insecure:
address: ':80'
secure:
address: ':443'
# Dynamic configuration will come from docker labels
providers:
docker:
endpoint: 'unix:///var/run/docker.sock'
network: 'proxy'
exposedByDefault: false
# Enable acme with http file challenge
certificatesResolvers:
le:
acme:
email: me@example.org
storage: /data/acme.json
httpChallenge:
entryPoint: insecure
```
**Run**
```sh
docker network create proxy
docker-compose up -d
```
## Cryptgeon
Create another docker-compose.yaml file in another folder. We will assume that the domain is `cryptgeon.example.org`.
```sh
/foo/bar/cryptgeon/
└── docker-compose.yaml
```
```yaml
version: '3.8'
networks:
proxy:
external: true
services:
redis:
image: redis:7-alpine
restart: unless-stopped
app:
image: cupcakearmy/cryptgeon:latest
restart: unless-stopped
depends_on:
- redis
environment:
SIZE_LIMIT: 4 MiB
networks:
- default
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.cryptgeon.rule=Host(`cryptgeon.example.org`)
- traefik.http.routers.cryptgeon.entrypoints=secure
- traefik.http.routers.cryptgeon.tls.certresolver=le
```
**Run**
```sh
docker-compose up -d
```
## Watchtower
> A container-based solution for automating Docker container base image updates.
[Watchtower](https://containrrr.dev/watchtower/) will keep our containers up to date. The interval is set to once a day and also configured to delete old images to prevent cluttering.
```sh
/foo/bar/watchtower/
└── docker-compose.yaml
```
```yaml
# docker-compose.yaml
version: '3.8'
services:
watchtower:
image: containrrr/watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --cleanup --interval 86400
```

View File

@ -0,0 +1,36 @@
# Install Cryptgeon with Traefik
Assumptions:
- Traefik 2 installed.
- External proxy docker network `proxy`.
- A certificate resolver `le`.
- A https entrypoint `secure`.
- Domain name `example.org`.
```yaml
version: '3.8'
networks:
proxy:
external: true
services:
redis:
image: redis:7-alpine
restart: unless-stopped
app:
image: cupcakearmy/cryptgeon:latest
restart: unless-stopped
depends_on:
- redis
networks:
- default
- proxy
labels:
- traefik.enable=true
- traefik.http.routers.cryptgeon.rule=Host(`example.org`)
- traefik.http.routers.cryptgeon.entrypoints=secure
- traefik.http.routers.cryptgeon.tls.certresolver=le
```

View File

@ -1,17 +1,21 @@
{
"scripts": {
"dev:docker": "docker-compose up memcached",
"dev:backend": "cargo watch -x 'run --bin cryptgeon'",
"dev:front": "pnpm --prefix client run dev",
"dev:proxy": "node proxy.mjs",
"dev": "run-p dev:*"
"dev:docker": "docker-compose -f docker-compose.dev.yaml up redis",
"dev:packages": "pnpm --parallel run dev",
"dev": "run-p dev:*",
"docker:up": "docker compose -f docker-compose.dev.yaml up",
"docker:build": "docker compose -f docker-compose.dev.yaml build",
"test": "playwright test --project chrome firefox safari",
"test:local": "playwright test --project chrome",
"test:server": "run-s docker:up",
"test:prepare": "run-p build docker:build",
"build": "pnpm run --recursive --filter=!@cryptgeon/backend build"
},
"devDependencies": {
"http-proxy": "^1.18.1",
"npm-run-all": "^4.1.5"
"@playwright/test": "^1.42.1",
"@types/node": "^20.11.28",
"npm-run-all": "^4.1.5",
"shelljs": "^0.8.5"
},
"dependencies": {
"file-saver": "^2.0.5",
"pretty-bytes": "^5.6.0"
}
"packageManager": "pnpm@8.15.4"
}

1680
packages/backend/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,25 @@
[package]
name = "cryptgeon"
version = "1.3.0"
version = "2.6.1"
authors = ["cupcakearmy <hi@nicco.io>"]
edition = "2018"
edition = "2021"
rust-version = "1.76"
[[bin]]
name = "cryptgeon"
path = "src/main.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "3"
actix-files = "0.5"
serde = "1"
actix-web = "4"
actix-files = "0.6"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1"
lazy_static = "1"
ring = "0.16"
bs62 = "0.1"
memcache = "0.16"
byte-unit = "4"
dotenv = "0.15"
mime = "0.3"
env_logger = "0.9"
log = "0.4"
redis = "0.23"

View File

@ -0,0 +1,10 @@
{
"private": true,
"name": "@cryptgeon/backend",
"scripts": {
"dev": "cargo watch -x 'run --bin cryptgeon'",
"build": "cargo build --release",
"test:server": "SIZE_LIMIT=10MiB LISTEN_ADDR=0.0.0.0:1234 cargo run",
"test:prepare": "cargo build"
}
}

View File

@ -0,0 +1,14 @@
use actix_web::web;
use crate::health;
use crate::note;
use crate::status;
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(
web::scope("/api")
.service(note::init())
.service(status::init())
.service(health::init()),
);
}

View File

@ -0,0 +1,17 @@
use actix_files::{Files, NamedFile};
use actix_web::{web, Result};
use crate::config;
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(
Files::new("/", config::FRONTEND_PATH.to_string())
.index_file("index.html")
.use_etag(true),
);
}
pub async fn index() -> Result<NamedFile> {
let index = format!("{}{}", config::FRONTEND_PATH.to_string(), "/index.html");
Ok(NamedFile::open(index)?)
}

View File

@ -0,0 +1,65 @@
use byte_unit::Byte;
// Internal
lazy_static! {
pub static ref VERSION: String = option_env!("CARGO_PKG_VERSION")
.unwrap_or("Unknown")
.to_string();
pub static ref FRONTEND_PATH: String =
std::env::var("FRONTEND_PATH").unwrap_or("../frontend/build".to_string());
pub static ref LISTEN_ADDR: String =
std::env::var("LISTEN_ADDR").unwrap_or("0.0.0.0:8000".to_string());
pub static ref VERBOSITY: String = std::env::var("VERBOSITY").unwrap_or("warn".to_string());
}
// CONFIG
lazy_static! {
pub static ref LIMIT: usize =
Byte::from_str(std::env::var("SIZE_LIMIT").unwrap_or("1 KiB".to_string()))
.unwrap()
.get_bytes() as usize;
pub static ref MAX_VIEWS: u32 = std::env::var("MAX_VIEWS")
.unwrap_or("100".to_string())
.parse()
.unwrap();
pub static ref MAX_EXPIRATION: u32 = std::env::var("MAX_EXPIRATION")
.unwrap_or("360".to_string()) // 6 hours in minutes
.parse()
.unwrap();
pub static ref ALLOW_ADVANCED: bool = std::env::var("ALLOW_ADVANCED")
.unwrap_or("true".to_string())
.parse()
.unwrap();
pub static ref ID_LENGTH: u32 = std::env::var("ID_LENGTH")
.unwrap_or("32".to_string())
.parse()
.unwrap();
pub static ref ALLOW_FILES: bool = std::env::var("ALLOW_FILES")
.unwrap_or("true".to_string())
.parse()
.unwrap();
pub static ref THEME_NEW_NOTE_NOTICE: bool = std::env::var("THEME_NEW_NOTE_NOTICE")
.unwrap_or("true".to_string())
.parse()
.unwrap();
}
// THEME
lazy_static! {
pub static ref THEME_IMAGE: String = std::env::var("THEME_IMAGE")
.unwrap_or("".to_string())
.parse()
.unwrap();
pub static ref THEME_TEXT: String = std::env::var("THEME_TEXT")
.unwrap_or("".to_string())
.parse()
.unwrap();
pub static ref THEME_PAGE_TITLE: String = std::env::var("THEME_PAGE_TITLE")
.unwrap_or("".to_string())
.parse()
.unwrap();
pub static ref THEME_FAVICON: String = std::env::var("THEME_FAVICON")
.unwrap_or("".to_string())
.parse()
.unwrap();
}

View File

@ -0,0 +1,3 @@
mod routes;
pub use routes::*;

View File

@ -0,0 +1,16 @@
use actix_web::{get, web, HttpResponse, Responder, Scope};
use crate::store;
#[get("/")]
async fn get_live() -> impl Responder {
if store::can_reach_redis() {
return HttpResponse::Ok();
} else {
return HttpResponse::ServiceUnavailable();
}
}
pub fn init() -> Scope {
web::scope("/live").service(get_live)
}

View File

@ -0,0 +1,44 @@
use actix_web::{
middleware::{self, Logger},
web::{self},
App, HttpServer,
};
use dotenv::dotenv;
use log::error;
#[macro_use]
extern crate lazy_static;
mod api;
mod client;
mod config;
mod health;
mod note;
mod size;
mod status;
mod store;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv().ok();
env_logger::init_from_env(env_logger::Env::new().default_filter_or(config::VERBOSITY.as_str()));
if !store::can_reach_redis() {
error!("cannot reach redis");
panic!("canont reach redis");
}
return HttpServer::new(|| {
App::new()
.wrap(Logger::new("\"%r\" %s %b %T"))
.wrap(middleware::Compress::default())
.wrap(middleware::DefaultHeaders::default())
.configure(size::init)
.configure(api::init)
.configure(client::init)
.default_service(web::to(client::index))
})
.bind(config::LISTEN_ADDR.to_string())?
.run()
.await;
}

View File

@ -0,0 +1,36 @@
use bs62;
use ring::rand::SecureRandom;
use serde::{Deserialize, Serialize};
use crate::config;
#[derive(Serialize, Deserialize, Clone)]
pub struct Note {
pub meta: String,
pub contents: String,
pub views: Option<u32>,
pub expiration: Option<u32>,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct NoteInfo {
pub meta: String,
}
#[derive(Serialize, Deserialize, Clone)]
pub struct NotePublic {
pub meta: String,
pub contents: String,
}
pub fn generate_id() -> String {
let mut result = "".to_owned();
let mut id: [u8; 1] = [0; 1];
let sr = ring::rand::SystemRandom::new();
for _ in 0..*config::ID_LENGTH {
let _ = sr.fill(&mut id);
result.push_str(&bs62::encode_data(&id));
}
return result;
}

View File

@ -0,0 +1,143 @@
use actix_web::{delete, get, post, web, HttpResponse, Responder, Scope};
use serde::{Deserialize, Serialize};
use std::time::SystemTime;
use crate::config;
use crate::note::{generate_id, Note, NoteInfo, NotePublic};
use crate::store;
pub fn now() -> u32 {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs() as u32
}
#[derive(Serialize, Deserialize)]
struct NotePath {
id: String,
}
#[get("/{id}")]
async fn one(path: web::Path<NotePath>) -> impl Responder {
let p = path.into_inner();
let note = store::get(&p.id);
match note {
Ok(Some(n)) => HttpResponse::Ok().json(NoteInfo { meta: n.meta }),
Ok(None) => HttpResponse::NotFound().finish(),
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[derive(Serialize, Deserialize)]
struct CreateResponse {
id: String,
}
#[post("/")]
async fn create(note: web::Json<Note>) -> impl Responder {
let mut n = note.into_inner();
let id = generate_id();
let bad_req = HttpResponse::BadRequest().finish();
if n.views == None && n.expiration == None {
return bad_req;
}
if !*config::ALLOW_ADVANCED {
n.views = Some(1);
n.expiration = None;
}
match n.views {
Some(v) => {
if v > *config::MAX_VIEWS || v < 1 {
return bad_req;
}
n.expiration = None; // views overrides expiration
}
_ => {}
}
match n.expiration {
Some(e) => {
if e > *config::MAX_EXPIRATION {
return bad_req;
}
let expiration = now() + (e * 60);
n.expiration = Some(expiration);
}
_ => {}
}
match store::set(&id.clone(), &n.clone()) {
Ok(_) => return HttpResponse::Ok().json(CreateResponse { id: id }),
Err(e) => return HttpResponse::InternalServerError().body(e.to_string()),
}
}
#[delete("/{id}")]
async fn delete(path: web::Path<NotePath>) -> impl Responder {
let p = path.into_inner();
let note = store::get(&p.id);
match note {
Err(e) => HttpResponse::InternalServerError().body(e.to_string()),
Ok(None) => return HttpResponse::NotFound().finish(),
Ok(Some(note)) => {
let mut changed = note.clone();
if changed.views == None && changed.expiration == None {
return HttpResponse::BadRequest().finish();
}
match changed.views {
Some(v) => {
changed.views = Some(v - 1);
let id = p.id.clone();
if v <= 1 {
match store::del(&id) {
Err(e) => {
return HttpResponse::InternalServerError().body(e.to_string())
}
_ => {}
}
} else {
match store::set(&id, &changed.clone()) {
Err(e) => {
return HttpResponse::InternalServerError().body(e.to_string())
}
_ => {}
}
}
}
_ => {}
}
let n = now();
match changed.expiration {
Some(e) => {
if e < n {
match store::del(&p.id.clone()) {
Ok(_) => return HttpResponse::BadRequest().finish(),
Err(e) => {
return HttpResponse::InternalServerError().body(e.to_string())
}
}
}
}
_ => {}
}
return HttpResponse::Ok().json(NotePublic {
contents: changed.contents,
meta: changed.meta,
});
}
}
}
#[derive(Serialize, Deserialize)]
struct Status {
version: String,
max_size: usize,
}
pub fn init() -> Scope {
web::scope("/notes")
.service(one)
.service(create)
.service(delete)
}

View File

@ -0,0 +1,12 @@
use crate::config;
use actix_web::web;
use mime;
pub fn init(cfg: &mut web::ServiceConfig) {
let json = web::JsonConfig::default().limit(*config::LIMIT);
let plain = web::PayloadConfig::default()
.limit(*config::LIMIT)
.mimetype(mime::STAR_STAR);
// cfg.app_data(plain);
cfg.app_data(json).app_data(plain);
}

View File

@ -0,0 +1,5 @@
mod model;
mod routes;
pub use model::*;
pub use routes::*;

View File

@ -0,0 +1,19 @@
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
pub struct Status {
// General
pub version: String,
// Config
pub max_size: u32,
pub max_views: u32,
pub max_expiration: u32,
pub allow_advanced: bool,
pub allow_files: bool,
pub theme_new_note_notice: bool,
// Theme
pub theme_image: String,
pub theme_text: String,
pub theme_page_title: String,
pub theme_favicon: String,
}

View File

@ -0,0 +1,25 @@
use actix_web::{get, web, HttpResponse, Responder, Scope};
use crate::config;
use crate::status::Status;
#[get("/")]
async fn get_status() -> impl Responder {
return HttpResponse::Ok().json(Status {
version: config::VERSION.to_string(),
max_size: *config::LIMIT as u32,
max_views: *config::MAX_VIEWS,
max_expiration: *config::MAX_EXPIRATION,
allow_advanced: *config::ALLOW_ADVANCED,
allow_files: *config::ALLOW_FILES,
theme_new_note_notice: *config::THEME_NEW_NOTE_NOTICE,
theme_image: config::THEME_IMAGE.to_string(),
theme_text: config::THEME_TEXT.to_string(),
theme_page_title: config::THEME_PAGE_TITLE.to_string(),
theme_favicon: config::THEME_FAVICON.to_string(),
});
}
pub fn init() -> Scope {
web::scope("/status").service(get_status)
}

View File

@ -0,0 +1,63 @@
use redis;
use redis::Commands;
use crate::note::now;
use crate::note::Note;
lazy_static! {
static ref REDIS_CLIENT: String = std::env::var("REDIS")
.unwrap_or("redis://127.0.0.1/".to_string())
.parse()
.unwrap();
}
fn get_connection() -> Result<redis::Connection, &'static str> {
let client =
redis::Client::open(REDIS_CLIENT.to_string()).map_err(|_| "Unable to connect to redis")?;
client
.get_connection()
.map_err(|_| "Unable to connect to redis")
}
pub fn can_reach_redis() -> bool {
let conn = get_connection();
return match conn {
Ok(_) => true,
Err(_) => false,
};
}
pub fn set(id: &String, note: &Note) -> Result<(), &'static str> {
let serialized = serde_json::to_string(&note.clone()).unwrap();
let mut conn = get_connection()?;
conn.set(id, serialized)
.map_err(|_| "Unable to set note in redis")?;
match note.expiration {
Some(e) => {
let seconds = e - now();
conn.expire(id, seconds as usize)
.map_err(|_| "Unable to set expiration on notion")?
}
None => {}
};
Ok(())
}
pub fn get(id: &String) -> Result<Option<Note>, &'static str> {
let mut conn = get_connection()?;
let value: Option<String> = conn.get(id).map_err(|_| "Could not load note in redis")?;
match value {
None => return Ok(None),
Some(s) => {
let deserialize: Note = serde_json::from_str(&s).unwrap();
return Ok(Some(deserialize));
}
}
}
pub fn del(id: &String) -> Result<(), &'static str> {
let mut conn = get_connection()?;
conn.del(id).map_err(|_| "Unable to delete note in redis")?;
Ok(())
}

54
packages/cli/README.md Normal file
View File

@ -0,0 +1,54 @@
# Cryptgeon CLI
The CLI is a functionally identical way to interact with cryptgeon notes.
It supports text, files, expiration, password, etc.
## Installation
```bash
npx cryptgeon
# Or install globally
npm -g install cryptgeon
cryptgeon
```
## Examples
```bash
# Create simple note
cryptgeon send text "Foo bar"
# Send two files
cryptgeon send file my.pdf picture.png
# 3 views
cryptgeon send text "My message" --views 3
# 10 minutes
cryptgeon send text "My message" --minutes 10
# Custom password
cryptgeon send text "My message" --password "1337"
# Password from stdin
echo "1337" | cryptgeon send text "My message"
# Open a link
cryptgeon open https://cryptgeon.org/note/16gOIkxWjCxYNuXM8tCqMUzl...
```
## Options
### Custom server
The default server is `cryptgeon.org`, however you can use any cryptgeon server by passing the `-s` or `--server` option, or by setting the `CRYPTGEON_SERVER` environment variable.
### Password
Optionally, just like in the web ui, you can choose to use a manual password. You can do that by passing the `-p` or `--password` options, or by piping it into stdin.
```bash
echo "my pw" | cryptgeon send text "my text"
cat pass.txt | cryptgeon send text "my text"
```

44
packages/cli/package.json Normal file
View File

@ -0,0 +1,44 @@
{
"name": "cryptgeon",
"version": "2.6.1",
"homepage": "https://github.com/cupcakearmy/cryptgeon",
"repository": {
"type": "git",
"url": "https://github.com/cupcakearmy/cryptgeon.git",
"directory": "packages/cli"
},
"type": "module",
"exports": {
".": "./dist/index.mjs"
},
"types": "./dist/index.d.ts",
"bin": {
"cryptgeon": "./dist/cli.cjs"
},
"files": [
"dist"
],
"scripts": {
"bin": "run-s build package",
"build": "rm -rf dist && tsc && ./scripts/build.js",
"dev": "./scripts/build.js --watch",
"prepublishOnly": "run-s build"
},
"devDependencies": {
"@commander-js/extra-typings": "^12.0.1",
"@cryptgeon/shared": "workspace:*",
"@types/inquirer": "^9.0.7",
"@types/mime": "^3.0.4",
"@types/node": "^20.11.24",
"commander": "^12.0.0",
"esbuild": "^0.20.1",
"inquirer": "^9.2.15",
"mime": "^4.0.1",
"occulto": "^2.0.3",
"pretty-bytes": "^6.1.1",
"typescript": "^5.3.3"
},
"engines": {
"node": ">=18"
}
}

34
packages/cli/scripts/build.js Executable file
View File

@ -0,0 +1,34 @@
#!/usr/bin/env node
import { build, context } from 'esbuild'
import pkg from '../package.json' assert { type: 'json' }
const common = {
bundle: true,
minify: true,
platform: 'node',
define: { VERSION: `"${pkg.version}"` },
}
const cliOptions = {
...common,
entryPoints: ['./src/cli.ts'],
format: 'cjs',
outfile: './dist/cli.cjs',
}
const indexOptions = {
...common,
entryPoints: ['./src/index.ts'],
outfile: './dist/index.mjs',
format: 'esm',
}
const watch = process.argv.slice(2)[0] === '--watch'
if (watch) {
const ctx = await context(cliOptions)
ctx.watch()
} else {
await build(cliOptions)
await build(indexOptions)
}

106
packages/cli/src/cli.ts Normal file
View File

@ -0,0 +1,106 @@
#!/usr/bin/env node
import { Argument, Option, program } from '@commander-js/extra-typings'
import { setBase, status } from '@cryptgeon/shared'
import prettyBytes from 'pretty-bytes'
import { download } from './download.js'
import { parseFile, parseNumber } from './parsers.js'
import { getStdin } from './stdin.js'
import { upload } from './upload.js'
import { checkConstrains, exit } from './utils.js'
const defaultServer = process.env['CRYPTGEON_SERVER'] || 'https://cryptgeon.org'
const server = new Option('-s --server <url>', 'the cryptgeon server to use').default(defaultServer)
const files = new Argument('<file...>', 'Files to be sent').argParser(parseFile)
const text = new Argument('<text>', 'Text content of the note')
const password = new Option('-p --password <string>', 'manually set a password')
const all = new Option('-a --all', 'Save all files without prompt').default(false)
const url = new Argument('<url>', 'The url to open')
const views = new Option('-v --views <number>', 'Amount of views before getting destroyed').argParser(parseNumber)
const minutes = new Option('-m --minutes <number>', 'Minutes before the note expires').argParser(parseNumber)
// Node 18 guard
parseInt(process.version.slice(1).split(',')[0]) < 18 && exit('Node 18 or higher is required')
// @ts-ignore
const version: string = VERSION
program.name('cryptgeon').version(version).configureHelp({ showGlobalOptions: true })
program
.command('info')
.description('show information about the server')
.addOption(server)
.action(async (options) => {
setBase(options.server)
const response = await status()
const formatted = {
...response,
max_size: prettyBytes(response.max_size),
}
for (const key of Object.keys(formatted)) {
if (key.startsWith('theme_')) delete formatted[key as keyof typeof formatted]
}
console.table(formatted)
})
const send = program.command('send').description('send a note')
send
.command('file')
.addArgument(files)
.addOption(server)
.addOption(views)
.addOption(minutes)
.addOption(password)
.action(async (files, options) => {
setBase(options.server!)
await checkConstrains(options)
options.password ||= await getStdin()
try {
const url = await upload(files, { views: options.views, expiration: options.minutes, password: options.password })
console.log(`Note created:\n\n${url}`)
} catch {
exit('Could not create note')
}
})
send
.command('text')
.addArgument(text)
.addOption(server)
.addOption(views)
.addOption(minutes)
.addOption(password)
.action(async (text, options) => {
setBase(options.server!)
await checkConstrains(options)
options.password ||= await getStdin()
try {
const url = await upload(text, { views: options.views, expiration: options.minutes, password: options.password })
console.log(`Note created:\n\n${url}`)
} catch {
exit('Could not create note')
}
})
program
.command('open')
.description('open a link with text or files inside')
.addArgument(url)
.addOption(password)
.addOption(all)
.action(async (note, options) => {
try {
const url = new URL(note)
options.password ||= await getStdin()
try {
await download(url, options.all, options.password)
} catch (e) {
exit(e instanceof Error ? e.message : 'Unknown error occurred')
}
} catch {
exit('Invalid URL')
}
})
program.parse()

View File

@ -0,0 +1,89 @@
import { Adapters, get, info, setBase } from '@cryptgeon/shared'
import inquirer from 'inquirer'
import { access, constants, writeFile } from 'node:fs/promises'
import { basename, resolve } from 'node:path'
import { AES, Hex } from 'occulto'
import pretty from 'pretty-bytes'
export async function download(url: URL, all: boolean, suggestedPassword?: string) {
setBase(url.origin)
const id = url.pathname.split('/')[2]
const preview = await info(id).catch(() => {
throw new Error('Note does not exist or is expired')
})
// Password
let password: string
const derivation = preview?.meta.derivation
if (derivation) {
if (suggestedPassword) {
password = suggestedPassword
} else {
const response = await inquirer.prompt([
{
type: 'password',
message: 'Note password',
name: 'password',
},
])
password = response.password
}
} else {
password = url.hash.slice(1)
}
const key = derivation ? (await AES.derive(password, derivation))[0] : Hex.decode(password)
const note = await get(id)
const couldNotDecrypt = new Error('Could not decrypt note. Probably an invalid password')
switch (note.meta.type) {
case 'file':
const files = await Adapters.Files.decrypt(note.contents, key).catch(() => {
throw couldNotDecrypt
})
if (!files) {
throw new Error('No files found in note')
}
let selected: typeof files
if (all) {
selected = files
} else {
const { names } = await inquirer.prompt([
{
type: 'checkbox',
message: 'What files should be saved?',
name: 'names',
choices: files.map((file) => ({
value: file.name,
name: `${file.name} - ${file.type} - ${pretty(file.size, { binary: true })}`,
checked: true,
})),
},
])
selected = files.filter((file) => names.includes(file.name))
}
if (!selected.length) throw new Error('No files selected')
await Promise.all(
selected.map(async (file) => {
let filename = resolve(file.name)
try {
// If exists -> prepend timestamp to not overwrite the current file
await access(filename, constants.R_OK)
filename = resolve(`${Date.now()}-${file.name}`)
} catch {}
await writeFile(filename, file.contents)
console.log(`Saved: ${basename(filename)}`)
})
)
break
case 'text':
const plaintext = await Adapters.Text.decrypt(note.contents, key).catch(() => {
throw couldNotDecrypt
})
console.log(plaintext)
break
}
}

View File

@ -0,0 +1,4 @@
export * from '@cryptgeon/shared'
export * from './download.js'
export * from './upload.js'
export * from './utils.js'

View File

@ -0,0 +1,27 @@
import { InvalidArgumentError, InvalidOptionArgumentError } from '@commander-js/extra-typings'
import { accessSync, constants } from 'node:fs'
import { resolve } from 'node:path'
export function parseFile(value: string, before: string[] = []) {
try {
const file = resolve(value)
accessSync(file, constants.R_OK)
return [...before, file]
} catch {
throw new InvalidArgumentError('cannot access file')
}
}
export function parseURL(value: string, _: URL): URL {
try {
return new URL(value)
} catch {
throw new InvalidArgumentError('is not a valid url')
}
}
export function parseNumber(value: string, _: number): number {
const n = parseInt(value, 10)
if (isNaN(n)) throw new InvalidOptionArgumentError('invalid number')
return n
}

24
packages/cli/src/stdin.ts Normal file
View File

@ -0,0 +1,24 @@
export function getStdin(timeout: number = 10): Promise<string> {
return new Promise<string>((resolve, reject) => {
// Store the data from stdin in a buffer
let buffer = ''
let t: NodeJS.Timeout
const dataHandler = (d: Buffer) => (buffer += d.toString())
const endHandler = () => {
clearTimeout(t)
resolve(buffer.trim())
}
// Stop listening for data after the timeout, otherwise hangs indefinitely
t = setTimeout(() => {
process.stdin.removeListener('data', dataHandler)
process.stdin.removeListener('end', endHandler)
process.stdin.pause()
resolve('')
}, timeout)
process.stdin.on('data', dataHandler)
process.stdin.on('end', endHandler)
})
}

View File

@ -0,0 +1,45 @@
import { readFile, stat } from 'node:fs/promises'
import { basename } from 'node:path'
import { Adapters, BASE, create, FileDTO, Note, NoteMeta } from '@cryptgeon/shared'
import mime from 'mime'
import { AES, Hex } from 'occulto'
export type UploadOptions = Pick<Note, 'views' | 'expiration'> & { password?: string }
export async function upload(input: string | string[], options: UploadOptions): Promise<string> {
const { password, ...noteOptions } = options
const derived = options.password ? await AES.derive(options.password) : undefined
const key = derived ? derived[0] : await AES.generateKey()
let contents: string
let type: NoteMeta['type']
if (typeof input === 'string') {
contents = await Adapters.Text.encrypt(input, key)
type = 'text'
} else {
const files: FileDTO[] = await Promise.all(
input.map(async (path) => {
const data = new Uint8Array(await readFile(path))
const stats = await stat(path)
const extension = path.substring(path.indexOf('.') + 1)
const type = mime.getType(extension) ?? 'application/octet-stream'
return {
name: basename(path),
size: stats.size,
contents: data,
type,
} satisfies FileDTO
})
)
contents = await Adapters.Files.encrypt(files, key)
type = 'file'
}
// Create the actual note and upload it.
const note: Note = { ...noteOptions, contents, meta: { type, derivation: derived?.[1] } }
const result = await create(note)
let url = `${BASE}/note/${result.id}`
if (!derived) url += `#${Hex.encode(key)}`
return url
}

19
packages/cli/src/utils.ts Normal file
View File

@ -0,0 +1,19 @@
import { status } from '@cryptgeon/shared'
import { exit as exitNode } from 'node:process'
export function exit(message: string) {
console.error(message)
exitNode(1)
}
export async function checkConstrains(constrains: { views?: number; minutes?: number }) {
const { views, minutes } = constrains
if (views && minutes) exit('cannot set view and minutes constrains simultaneously')
if (!views && !minutes) constrains.views = 1
const response = await status()
if (views && views > response.max_views)
exit(`Only a maximum of ${response.max_views} views allowed. ${views} given.`)
if (minutes && minutes > response.max_expiration)
exit(`Only a maximum of ${response.max_expiration} minutes allowed. ${minutes} given.`)
}

View File

@ -0,0 +1,13 @@
{
"compilerOptions": {
"target": "es2022",
"module": "es2022",
"moduleResolution": "node",
"declaration": true,
"emitDeclarationOnly": true,
"strict": true,
"outDir": "./dist",
"rootDir": "./src",
"allowSyntheticDefaultImports": true
}
}

View File

@ -0,0 +1,18 @@
# Cryptgeon Frontend
## Locale
Download with these settings:
```json
{
"format": "json",
"indentation": "tab",
"json_unescaped_slashes": true,
"export_sort": "first_added",
"original_filenames": false,
"export_empty_as": "skip",
"add_newline_eof": true,
"replace_breaks": false
}
```

View File

@ -0,0 +1,8 @@
├─ MIT: 13
├─ ISC: 2
├─ BSD-3-Clause: 1
├─ (MPL-2.0 OR Apache-2.0): 1
├─ BSD-2-Clause: 1
├─ 0BSD: 1
└─ Apache-2.0: 1
1 ├─ MIT: 13
2 ├─ ISC: 2
3 ├─ BSD-3-Clause: 1
4 ├─ (MPL-2.0 OR Apache-2.0): 1
5 ├─ BSD-2-Clause: 1
6 ├─ 0BSD: 1
7 └─ Apache-2.0: 1

View File

@ -0,0 +1,58 @@
{
"common": {
"note": "Notiz",
"file": "Datei",
"advanced": "Erweiterte Optionen",
"create": "Erstellen",
"loading": "Lädt...",
"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",
"copied_to_clipboard": "In die Zwischenablage kopiert.",
"encrypting": "Wird verschlüsselt...",
"decrypting": "Wird entschlüsselt...",
"uploading": "Hochladen",
"downloading": "Wird heruntergeladen",
"qr_code": "QR-Code",
"password": "Passwort"
},
"home": {
"intro": "Erstellen Sie mit einem Klick <i>vollständig verschlüsselte</i>, sichere Notizen oder Dateien und teilen Sie diese über einen Link.",
"explanation": "Die Notiz verfällt nach {type}.",
"new_note": "Neue Notiz",
"new_note_notice": "<b>Wichtiger Hinweis zur Verfügbarkeit:</b><br />Es kann nicht garantiert werden, dass diese Notiz gespeichert wird, da diese <b>ausschließlich im Speicher</b> gehalten werden. Ist dieser voll, werden die ältesten Notizen entfernt.<br />(Wahrscheinlich gibt es keine derartigen Probleme, seien Sie nur vorgewarnt).",
"errors": {
"note_to_big": "Notiz konnte nicht erstellt werden, da sie zu groß ist.",
"note_error": "Notiz konnte nicht erstellt werden. Bitte versuchen Sie es erneut.",
"max": "max: {n}",
"empty_content": "Notiz ist leer."
},
"messages": {
"note_created": "Notiz wurde erstellt."
},
"advanced": {
"explanation": "Standardmäßig wird für jede Notiz ein generiertes, sicheres Passwort verwendet. Alternativ können Sie ein eigenes Kennwort festlegen, welches nicht im Link enthalten ist.",
"custom_password": "Benutzerdefiniertes Passwort"
}
},
"show": {
"errors": {
"not_found": "Notiz konnte nicht gefunden werden oder wurde bereits gelöscht.",
"decryption_failed": "Notiz konnte nicht entschlüsselt werden. Vermutlich ist das Passwort falsch oder der Link defekt. Die Notiz wurde daher gelöscht.",
"unsupported_type": "Nicht unterstützter Notiztyp."
},
"explanation": "Klicken Sie auf den Button, um die Notiz anzuzeigen und anschließend zu löschen, falls ein festgelegtes Limit erreicht wurde.",
"show_note": "Notiz anzeigen",
"warning_will_not_see_again": "ACHTUNG! Sie werden anschließend <b>keine</b> Gelegenheit mehr haben, die Notiz erneut anzusehen.",
"download_all": "Alle Dateien herunterladen",
"links_found": "Gefundene Links in der Notiz:"
},
"file_upload": {
"selected_files": "Ausgewählte Dateien",
"no_files_selected": "Keine Dateien ausgewählt",
"clear": "Zurücksetzen"
}
}

View File

@ -0,0 +1,58 @@
{
"common": {
"note": "note",
"file": "file",
"advanced": "advanced",
"create": "create",
"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",
"copied_to_clipboard": "copied to clipboard",
"encrypting": "encrypting",
"decrypting": "decrypting",
"uploading": "uploading",
"downloading": "downloading",
"qr_code": "qr code",
"password": "password"
},
"home": {
"intro": "Easily send <i>fully encrypted</i>, secure notes or files with one click. Just create a note and share the link.",
"explanation": "the note will expire and be destroyed after {type}.",
"new_note": "new note",
"new_note_notice": "<b>availability:</b><br />the note is not guaranteed to be stored as everything is kept in ram, if it fills up the oldest notes will be removed.<br />(you probably will be fine, just be warned.)",
"errors": {
"note_to_big": "could not create note. note is to big",
"note_error": "could not create note. please try again.",
"max": "max: {n}",
"empty_content": "note is empty."
},
"messages": {
"note_created": "note created."
},
"advanced": {
"explanation": "By default, a securely generated password is used for each note. You can however also choose your own password, which is not included in the link.",
"custom_password": "custom password"
}
},
"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.",
"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",
"warning_will_not_see_again": "you will <b>not</b> get the chance to see the note again.",
"download_all": "download all",
"links_found": "links found inside the note:"
},
"file_upload": {
"selected_files": "Selected Files",
"no_files_selected": "No Files Selected",
"clear": "Reset"
}
}

View File

@ -0,0 +1,58 @@
{
"common": {
"note": "nota",
"file": "archivo",
"advanced": "avanzado",
"create": "crear",
"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",
"copied_to_clipboard": "copiado al portapapeles",
"encrypting": "encriptando",
"decrypting": "descifrando",
"uploading": "cargando",
"downloading": "descargando",
"qr_code": "código qr",
"password": "contraseña"
},
"home": {
"intro": "Envía fácilmente notas o archivos <i>totalmente encriptados</i> y seguros con un solo clic. Solo tienes que crear una nota y compartir el enlace.",
"explanation": "la nota expirará y se destruirá después de {type}.",
"new_note": "nueva nota",
"new_note_notice": "<b>disponibilidad:</b><br />no se garantiza que la nota se almacene, ya que todo se guarda en la memoria RAM, si se llena se eliminarán las notas más antiguas.<br />(probablemente estará bien, solo está advertido.)",
"errors": {
"note_to_big": "no se pudo crear la nota. la nota es demasiado grande",
"note_error": "No se ha podido crear la nota. Por favor, inténtelo de nuevo.",
"max": "max: {n}",
"empty_content": "la nota está vacía."
},
"messages": {
"note_created": "nota creada."
},
"advanced": {
"explanation": "Por defecto, se utiliza una contraseña generada de forma segura para cada nota. No obstante, también puede elegir su propia contraseña, la cual no se incluye en el enlace.",
"custom_password": "contraseña personalizada"
}
},
"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.",
"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",
"warning_will_not_see_again": "<b>no</b> tendrás la oportunidad de volver a ver la nota.",
"download_all": "descargar todo",
"links_found": "enlaces que se encuentran dentro de la nota:"
},
"file_upload": {
"selected_files": "Archivos seleccionados",
"no_files_selected": "No hay archivos seleccionados",
"clear": "Restablecer"
}
}

View File

@ -0,0 +1,58 @@
{
"common": {
"note": "note",
"file": "fichier",
"advanced": "avancé",
"create": "créer",
"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",
"copied_to_clipboard": "copié dans le presse-papiers",
"encrypting": "chiffrer",
"decrypting": "déchiffrer",
"uploading": "téléversement",
"downloading": "téléchargement",
"qr_code": "code qr",
"password": "mot de passe"
},
"home": {
"intro": "Envoyez facilement des notes ou des fichiers <i>entièrement chiffrés</i> et sécurisés en un seul clic. Il suffit de créer une note et de partager le lien.",
"explanation": "la note expirera et sera détruite après {type}.",
"new_note": "nouvelle note",
"new_note_notice": "<b>disponibilité :</b><br />la note n'est pas garantie d'être stockée car tout est conservé dans la mémoire vive; si elle se remplit, les notes les plus anciennes seront supprimées.<br />(tout ira probablement bien, soyez juste averti.)",
"errors": {
"note_to_big": "Impossible de créer une note. La note est trop grande.",
"note_error": "n'a pas pu créer de note. Veuillez réessayer.",
"max": "max: {n}",
"empty_content": "La note est vide."
},
"messages": {
"note_created": "note créée."
},
"advanced": {
"explanation": "Par défaut, un mot de passe généré de manière sécurisée est utilisé pour chaque note. Vous pouvez toutefois choisir votre propre mot de passe, qui n'est pas inclus dans le lien.",
"custom_password": "mot de passe personnalisé"
}
},
"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.",
"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",
"warning_will_not_see_again": "vous <b>n'aurez pas</b> la chance de revoir la note.",
"download_all": "télécharger tout",
"links_found": "liens trouvés à lintérieur de la note :"
},
"file_upload": {
"selected_files": "Fichiers sélectionnés",
"no_files_selected": "Aucun fichier sélectionné",
"clear": "Réinitialiser"
}
}

View File

@ -0,0 +1,58 @@
{
"common": {
"note": "nota",
"file": "file",
"advanced": "avanzato",
"create": "crea",
"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",
"copied_to_clipboard": "copiato negli appunti",
"encrypting": "criptando",
"decrypting": "decifrando",
"uploading": "caricamento",
"downloading": "scaricando",
"qr_code": "codice qr",
"password": "password"
},
"home": {
"intro": "Invia facilmente note o file <i>completamente criptati</i> e sicuri con un solo clic. Basta creare una nota e condividere il link.",
"explanation": "la nota scadrà e sarà distrutta dopo {type}.",
"new_note": "nuova nota",
"new_note_notice": "<b>disponibilità:</b><br />la nota non è garantita per essere memorizzata come tutto è tenuto in ram, se si riempie le note più vecchie saranno rimosse.<br />(probabilmente andrà bene, basta essere avvertiti).",
"errors": {
"note_to_big": "impossibile creare una nota. la nota è troppo grande",
"note_error": "Impossibile creare la nota. Riprova.",
"max": "max: {n}",
"empty_content": "la nota è vuota."
},
"messages": {
"note_created": "nota creata."
},
"advanced": {
"explanation": "Per impostazione predefinita, per ogni nota viene utilizzata una password generata in modo sicuro. È tuttavia possibile scegliere la propria password, che non è inclusa nel link.",
"custom_password": "password personalizzata"
}
},
"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.",
"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",
"warning_will_not_see_again": "<b>non</b> avrete la possibilità di rivedere la nota.",
"download_all": "scarica tutti",
"links_found": "link presenti all'interno della nota:"
},
"file_upload": {
"selected_files": "File selezionati",
"no_files_selected": "Nessun file selezionato",
"clear": "Reset"
}
}

View File

@ -0,0 +1,58 @@
{
"common": {
"note": "新しいメモ",
"file": "ファイル",
"advanced": "アドバンスド",
"create": "作成",
"loading": "読み込み中",
"mode": "モード",
"views": "{n, plural, =0 {表示可能な時間} =1 { 1 ビュー} other {#ビュー}}",
"minutes": "{n, plural, =0 {有効期間(分)} =1 {1 分} other {# 分}}",
"max": "マックス",
"share_link": "共有リンク",
"copy_clipboard": "クリップボードにコピーする",
"copied_to_clipboard": "クリップボードにコピーされました",
"encrypting": "暗号化",
"decrypting": "復号化",
"uploading": "アップロード中",
"downloading": "ダウンロード中",
"qr_code": "QRコード",
"password": "暗号"
},
"home": {
"intro": "<i>完全に暗号化された</i> 、安全なメモやファイルをワンクリックで簡単に送信できます。メモを作成してリンクを共有するだけです。",
"explanation": "メモは{type}後に期限切れになり、破棄されます。",
"new_note": "新しいメモ",
"new_note_notice": "<b>可用性: </b> <br />すべてが RAM に保持されるため、メモが保存されるとは限りません。いっぱいになると、最も古いメモが削除されます。 <br /> (大丈夫だと思いますが、ご了承ください。)",
"errors": {
"note_to_big": "メモを作成できませんでした。メモが大きすぎる",
"note_error": "メモを作成できませんでした。もう一度お試しください。",
"max": "最大ファイルサイズ: {n}",
"empty_content": "メモは空です。"
},
"messages": {
"note_created": "メモが作成されました。"
},
"advanced": {
"explanation": "デフォルトでは、安全に生成されたパスワードが各ノートに使用されます。しかし、リンクに含まれない独自のパスワードを選択することもできます。",
"custom_password": "カスタムパスワード"
}
},
"show": {
"errors": {
"not_found": "メモが見つからないか、既に削除されています。",
"decryption_failed": "パスワードエラー!不完全なリンクを貼り付けたり、暗号を解読しようとしたりしている可能性があります!しかし、いずれにしても、この暗号は破棄されました!",
"unsupported_type": "サポートされていないメモ タイプです。"
},
"explanation": "カウンターが上限に達した場合、ノートの表示と削除を行うには、以下をクリックします。",
"show_note": "メモを表示",
"warning_will_not_see_again": "あなた <b>できません</b> このノートをもう一度見る",
"download_all": "すべてダウンロード",
"links_found": "メモ内にあるリンク:"
},
"file_upload": {
"selected_files": "選択したファイル",
"no_files_selected": "ファイルが選択されていません",
"clear": "リセット"
}
}

View File

@ -0,0 +1,58 @@
{
"common": {
"note": "notatka",
"file": "plik",
"advanced": "zaawansowane",
"create": "utwórz",
"loading": "ładowanie",
"mode": "tryb",
"views": "{n, plural, =0 {wyświetleń} =1 {1 wyświetlenie} other {# wyświetleń}}",
"minutes": "{n, plural, =0 {minut} =1 {1 minuta} other {# minuty}}",
"max": "maks.",
"share_link": "link udostępniania",
"copy_clipboard": "kopiuj do schowka",
"copied_to_clipboard": "skopiowano do schowka",
"encrypting": "szyfrowanie",
"decrypting": "odszyfrowywanie",
"uploading": "wysyłanie",
"downloading": "pobieranie",
"qr_code": "kod QR",
"password": "hasło"
},
"home": {
"intro": "Łatwo wysyłaj <i>w pełni zaszyfrowane</i>, bezpieczne notatki lub pliki jednym kliknięciem. Po prostu utwórz notatkę i udostępnij link.",
"explanation": "notatka wygaśnie i zostanie zniszczona po {type}.",
"new_note": "nowa notatka",
"new_note_notice": "<b>dostępność:</b><br />nie ma gwarancji, że notatka będzie przechowywana, ponieważ wszystko jest przechowywane w pamięci RAM, jeśli się zapełni, najstarsze notatki zostaną usunięte.<br />(prawdopodobnie nic się nie stanie, ale warto ostrzec.)",
"errors": {
"note_to_big": "nie można utworzyć notatki. notatka jest za duża",
"note_error": "nie można utworzyć notatki. spróbuj ponownie.",
"max": "maks .: {n}",
"empty_content": "notatka jest pusta."
},
"messages": {
"note_created": "notatka utworzona."
},
"advanced": {
"explanation": "Domyślnie dla każdej notatki używane jest bezpiecznie wygenerowane hasło. Możesz jednak wybrać własne hasło, które nie jest uwzględnione w linku.",
"custom_password": "własne hasło"
}
},
"show": {
"errors": {
"not_found": "notatka nie została znaleziona lub została już usunięta.",
"decryption_failed": "błędne hasło. nie można odszyfrować. prawdopodobnie uszkodzony link. notatka została zniszczona.",
"unsupported_type": "nieobsługiwany typ notatki."
},
"explanation": "kliknij poniżej, aby wyświetlić i usunąć notatkę, jeśli licznik osiągnie swój limit",
"show_note": "pokaż notatkę",
"warning_will_not_see_again": "<b>nie będziesz mieć</b> możliwości ponownego zobaczenia notatki.",
"download_all": "pobierz wszystko",
"links_found": "linki znalezione w notatce:"
},
"file_upload": {
"selected_files": "Wybrane pliki",
"no_files_selected": "Nie wybrano plików",
"clear": "Wyczyść"
}
}

View File

@ -0,0 +1,58 @@
{
"common": {
"note": "заметка",
"file": "файл",
"advanced": "расширенные",
"create": "создать",
"loading": "загрузка",
"mode": "режим",
"views": "{n, plural, =0 {просмотры} =1 {1 просмотр} other {# просмотры}}",
"minutes": "{n, plural, =0 {минут} =1 {1 минута} other {# минуты}}",
"max": "макс",
"share_link": "поделиться ссылкой",
"copy_clipboard": "скопировать в буфер обмена",
"copied_to_clipboard": "скопировано в буфер обмена",
"encrypting": "шифрование",
"decrypting": "расшифровка",
"uploading": "загрузка",
"downloading": "скачивание",
"qr_code": "qr код",
"password": "пароль"
},
"home": {
"intro": "Легко отправляйте <i>полностью зашифрованные</i> защищенные заметки или файлы одним щелчком мыши. Просто создайте заметку и поделитесь ссылкой.",
"explanation": "заметка истечет и будет уничтожена после {type}.",
"new_note": "новая заметка",
"new_note_notice": "<b>доступность:</b><br />сохранение заметки не гарантируется, поскольку все хранится в оперативной памяти; если она заполнится, самые старые заметки будут удалены.<br />( вероятно, все будет в порядке, просто будьте осторожны.)",
"errors": {
"note_to_big": "нельзя создать новую заметку. заметка слишком большая",
"note_error": "нельзя создать новую заметку. пожалйста попробуйте позднее.",
"max": "макс: {n}",
"empty_content": "пустая заметка."
},
"messages": {
"note_created": "заметка создана."
},
"advanced": {
"explanation": "По умолчанию для каждой заметки используется безопасно сгенерированный пароль. Однако вы также можете выбрать свой собственный пароль, который не включен в ссылку.",
"custom_password": "пользовательский пароль"
}
},
"show": {
"errors": {
"not_found": "заметка не найдена или была удалена.",
"decryption_failed": "неправильный пароль. не смог расшифровать. возможно ссылка битая. записка уничтожена.",
"unsupported_type": "неподдерживаемый тип заметки."
},
"explanation": "щелкните ниже, чтобы показать и удалить примечание, если счетчик достиг предела",
"show_note": "показать заметку",
"warning_will_not_see_again": "вы <b>не сможете</b> больше просмотреть заметку.",
"download_all": "скачать всё",
"links_found": "ссылки внутри заметки:"
},
"file_upload": {
"selected_files": "Выбранные файлы",
"no_files_selected": "Файлы не выбраны",
"clear": "Сброс"
}
}

View File

@ -0,0 +1,58 @@
{
"common": {
"note": "密信",
"file": "上传文件",
"advanced": "高级设置",
"create": "创建",
"loading": "加载中",
"mode": "模式",
"views": "{n, plural, =0 {可读次数} =1 {1 次查看} other {# 次查看}}",
"minutes": "{n, plural, =0 {有效期(分钟)} =1 {1 分钟} other {# 分钟}}",
"max": "最大值",
"share_link": "分享链接",
"copy_clipboard": "复制到粘贴板",
"copied_to_clipboard": "已成功复制到粘贴板",
"encrypting": "加密",
"decrypting": "解密",
"uploading": "上传",
"downloading": "下载",
"qr_code": "二维码",
"password": "密码"
},
"home": {
"intro": "飞鸽传书,一键传输完全加密的密信或文件,阅后即焚。",
"explanation": "该密信会在{type}后失效。",
"new_note": "新建密信",
"new_note_notice": "<b>提醒:</b><br>密信保存在内存中,如果内存满了,则最早的密信将被删除以释放内存,因此不保证该密信的可用性。一般不会出现这种情况,无需担心。",
"errors": {
"note_to_big": "创建失败,密信过大。",
"note_error": "创建失败,请稍后重试。",
"max": "次数上限:{n}",
"empty_content": "密信不能为空。"
},
"messages": {
"note_created": "密信创建成功。"
},
"advanced": {
"explanation": "默认情况下,每个笔记都使用安全生成的密码。但是,您也可以选择您自己的密码,该密码未包含在链接中。",
"custom_password": "自定义密码"
}
},
"show": {
"errors": {
"not_found": "密信不存在,可能已被查看或删除。",
"decryption_failed": "密钥错误,无法查看。",
"unsupported_type": "不支持的类型。"
},
"explanation": "点击下方按钮即可查看密信,阅后即焚。",
"show_note": "查看密信",
"warning_will_not_see_again": "你将<b>无法</b>再次查看该密信,请尽快复制到粘贴板。",
"download_all": "下载全部",
"links_found": "注释中找到的链接:"
},
"file_upload": {
"selected_files": "已选中的文件",
"no_files_selected": "没有文件被选中",
"clear": "重置"
}
}

View File

@ -0,0 +1,40 @@
{
"private": true,
"name": "@cryptgeon/web",
"scripts": {
"postinstall": "svelte-kit sync",
"dev": "vite dev",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-check --tsconfig tsconfig.json",
"licenses": "license-checker --summary > licenses.csv",
"locale:download": "node scripts/locale.js",
"test:prepare": "pnpm run build"
},
"type": "module",
"devDependencies": {
"@lokalise/node-api": "^12.1.0",
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.5.2",
"@sveltejs/vite-plugin-svelte": "^3.0.2",
"@types/file-saver": "^2.0.7",
"@zerodevx/svelte-toast": "^0.9.5",
"adm-zip": "^0.5.10",
"dotenv": "^16.4.5",
"svelte": "^4.2.12",
"svelte-check": "^3.6.6",
"svelte-intl-precompile": "^0.12.3",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"vite": "^5.1.7"
},
"dependencies": {
"@cryptgeon/shared": "workspace:*",
"@fontsource/fira-mono": "^5.0.8",
"copy-to-clipboard": "^3.3.3",
"file-saver": "^2.0.5",
"occulto": "^2.0.3",
"pretty-bytes": "^6.1.1",
"qrious": "^4.0.2"
}
}

Some files were not shown because too many files have changed in this diff Show More