Fully rewrote the server fr in go
This commit is contained in:
parent
99b8eb4240
commit
dde44016e3
34
APIDOCS.md
34
APIDOCS.md
|
@ -7,34 +7,9 @@ Headers should be: "Content-type: application/json; charset=UTF-8" for all POSTs
|
||||||
|
|
||||||
POST - /api/signup - provide "username" and "password".
|
POST - /api/signup - provide "username" and "password".
|
||||||
|
|
||||||
POST - /api/login - provide "username", "password", "passwordchange" (must be "yes" or "no") and "newpass"
|
POST - /api/login - provide "username", "password"
|
||||||
|
|
||||||
To prevent the server from knowing the encryption key, the password you provide in the request must be hashed with the SHA-3 with 128 iterations (the hash is hashed again 128 times).
|
|
||||||
|
|
||||||
If you wish to change the user's password, set "passwordchange" to "yes" and "newpass" to the new hash.
|
|
||||||
|
|
||||||
|
|
||||||
Some users use the legacy argon2 id mode (by which I mean about 8, so only implement if you feel like it), and to implement argon2 id functionality, you hash like this:
|
|
||||||
```
|
|
||||||
Parallelism should be 1
|
|
||||||
|
|
||||||
Iterations should be 256
|
|
||||||
|
|
||||||
Memory Allocated in bytes should be 512
|
|
||||||
|
|
||||||
Length of Hash should be 32 bytes
|
|
||||||
|
|
||||||
The output should be in the encoded format, not the hashed format
|
|
||||||
|
|
||||||
Salt should be the SHA512 of the password
|
|
||||||
```
|
|
||||||
|
|
||||||
(Yes I know these are really awful practice, guess why we are replacing it)
|
|
||||||
|
|
||||||
To test if SHA-3 or argon2 is used, just try the SHA-3 and if 422 gets returned try argon2.
|
|
||||||
|
|
||||||
(For the sake of all of us, change the password to the SHA-3 hash)
|
|
||||||
|
|
||||||
|
To prevent the server from knowing the encryption key, the password you provide in the request must be hashed with the SHA-3 algorithm with 128 iterations (the hash is hashed again 128 times).
|
||||||
|
|
||||||
Password should be at least 8 characters, username must be under 20 characters and alphanumeric.
|
Password should be at least 8 characters, username must be under 20 characters and alphanumeric.
|
||||||
|
|
||||||
|
@ -69,9 +44,6 @@ POST - /api/editnote - edit notes, provide "secretKey", "noteId", "title", and "
|
||||||
"content" should be encrypted.
|
"content" should be encrypted.
|
||||||
"title" is the first line of the note content, and should be encrypted.
|
"title" is the first line of the note content, and should be encrypted.
|
||||||
|
|
||||||
**(Deprecated ⚠️)** POST - /api/editnotetitle - edit note titles, provide "secretKey", "noteId", and "content"
|
|
||||||
"content" should be encrypted.
|
|
||||||
|
|
||||||
POST - /api/removenote - remove notes, provide "secretKey" and "noteId"
|
POST - /api/removenote - remove notes, provide "secretKey" and "noteId"
|
||||||
|
|
||||||
## ⚙️ More stuff
|
## ⚙️ More stuff
|
||||||
|
@ -85,3 +57,5 @@ note content and title will have to be decrypted
|
||||||
POST - /api/sessions/list - show all sessions, provide "secretKey"
|
POST - /api/sessions/list - show all sessions, provide "secretKey"
|
||||||
|
|
||||||
POST - /api/sessions/remove - remove session, provide "secretKey" and "sessionId"
|
POST - /api/sessions/remove - remove session, provide "secretKey" and "sessionId"
|
||||||
|
|
||||||
|
POST - /api/listusers - lists all users in JSON, provide "masterKey" (set in config.ini)
|
||||||
|
|
|
@ -5,12 +5,12 @@ Burgernotes is a simple note-taking app with end-to-end encryption.
|
||||||
To set up Burgernotes, simply run these commands:
|
To set up Burgernotes, simply run these commands:
|
||||||
```
|
```
|
||||||
cp config.ini.example config.ini
|
cp config.ini.example config.ini
|
||||||
python3 init_db
|
./burgernotes init_db
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit config.ini to your liking, then to start the server run:
|
Edit config.ini to your liking, then to start the server run:
|
||||||
```
|
```
|
||||||
python3 main
|
./burgernotes
|
||||||
```
|
```
|
||||||
### Links
|
### Links
|
||||||
[Go to the Burgernotes website](https://notes.hectabit.org)
|
[Go to the Burgernotes website](https://notes.hectabit.org)
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,57 @@
|
||||||
|
module hectabit.org/burgernotes
|
||||||
|
|
||||||
|
go 1.22.2
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gin-contrib/sessions v1.0.1
|
||||||
|
github.com/gin-gonic/gin v1.10.0
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22
|
||||||
|
github.com/spf13/viper v1.19.0
|
||||||
|
golang.org/x/crypto v0.24.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
|
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||||
|
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||||
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
|
github.com/gorilla/context v1.1.2 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.2 // indirect
|
||||||
|
github.com/gorilla/sessions v1.2.2 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
|
||||||
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
|
go.uber.org/multierr v1.9.0 // indirect
|
||||||
|
golang.org/x/arch v0.8.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
|
golang.org/x/net v0.25.0 // indirect
|
||||||
|
golang.org/x/sys v0.21.0 // indirect
|
||||||
|
golang.org/x/text v0.16.0 // indirect
|
||||||
|
google.golang.org/protobuf v1.34.1 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
|
@ -0,0 +1,142 @@
|
||||||
|
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
|
||||||
|
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
|
||||||
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
|
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||||
|
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||||
|
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||||
|
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
|
||||||
|
github.com/gin-contrib/sessions v1.0.1 h1:3hsJyNs7v7N8OtelFmYXFrulAf6zSR7nW/putcPEHxI=
|
||||||
|
github.com/gin-contrib/sessions v1.0.1/go.mod h1:ouxSFM24/OgIud5MJYQJLpy6AwxQ5EYO9yLhbtObGkM=
|
||||||
|
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||||
|
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||||
|
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||||
|
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||||
|
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||||
|
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||||
|
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||||
|
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
|
||||||
|
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||||
|
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
|
||||||
|
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
|
||||||
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||||
|
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
|
github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o=
|
||||||
|
github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM=
|
||||||
|
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||||
|
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||||
|
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||||
|
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||||
|
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||||
|
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||||
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
|
||||||
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
|
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||||
|
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
|
||||||
|
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||||
|
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
|
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
|
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
|
||||||
|
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
|
||||||
|
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
|
||||||
|
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||||
|
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
|
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
|
||||||
|
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
|
||||||
|
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||||
|
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||||
|
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
|
||||||
|
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||||
|
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
29
init_db
29
init_db
|
@ -1,29 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
|
|
||||||
|
|
||||||
def generatedb():
|
|
||||||
connection = sqlite3.connect("database.db")
|
|
||||||
|
|
||||||
with open("schema.sql") as f:
|
|
||||||
connection.executescript(f.read())
|
|
||||||
|
|
||||||
connection.commit()
|
|
||||||
connection.close()
|
|
||||||
|
|
||||||
print("[INFO] Generated database")
|
|
||||||
|
|
||||||
|
|
||||||
if not os.path.exists("database.db"):
|
|
||||||
generatedb()
|
|
||||||
else:
|
|
||||||
answer = input("Proceeding will overwrite the database. Proceed? (y/N)")
|
|
||||||
if "y" in answer.lower():
|
|
||||||
generatedb()
|
|
||||||
elif "n" in answer.lower():
|
|
||||||
print("Stopped")
|
|
||||||
elif ":3" in answer:
|
|
||||||
print(":3")
|
|
||||||
else:
|
|
||||||
print("Stopped")
|
|
576
main
576
main
|
@ -1,576 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
import os
|
|
||||||
import sqlite3
|
|
||||||
import time
|
|
||||||
import secrets
|
|
||||||
import configparser
|
|
||||||
import asyncio
|
|
||||||
from hypercorn.config import Config
|
|
||||||
from hypercorn.asyncio import serve
|
|
||||||
from werkzeug.security import generate_password_hash, check_password_hash
|
|
||||||
from quart import Quart, request
|
|
||||||
from json import loads
|
|
||||||
|
|
||||||
# Parse configuration file, and check if anything is wrong with it
|
|
||||||
if not os.path.exists("config.ini"):
|
|
||||||
print("[FATAL] config.ini does not exist")
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
config = configparser.ConfigParser()
|
|
||||||
config.read("config.ini")
|
|
||||||
print("[INFO] Config loaded at", str(int(time.time())))
|
|
||||||
|
|
||||||
HOST = config["config"]["HOST"]
|
|
||||||
PORT = config["config"]["PORT"]
|
|
||||||
SECRET_KEY = config["config"]["SECRET_KEY"]
|
|
||||||
MAX_STORAGE = config["config"]["MAX_STORAGE"]
|
|
||||||
|
|
||||||
if SECRET_KEY == "supersecretkey" or SECRET_KEY == "placeholder":
|
|
||||||
print("[WARNING] Secret key not set. Please set the secret key to a non-default value.")
|
|
||||||
|
|
||||||
# Define Quart
|
|
||||||
app = Quart(__name__)
|
|
||||||
app.config["SECRET_KEY"] = SECRET_KEY
|
|
||||||
|
|
||||||
|
|
||||||
# Database functions
|
|
||||||
def get_db_connection():
|
|
||||||
conn = sqlite3.connect("database.db")
|
|
||||||
conn.row_factory = sqlite3.Row
|
|
||||||
return conn
|
|
||||||
|
|
||||||
|
|
||||||
def get_user(identifier):
|
|
||||||
conn = get_db_connection()
|
|
||||||
post = conn.execute("SELECT * FROM users WHERE id = ?",
|
|
||||||
(identifier,)).fetchone()
|
|
||||||
conn.close()
|
|
||||||
if post is None:
|
|
||||||
return None
|
|
||||||
return post
|
|
||||||
|
|
||||||
|
|
||||||
def get_note(identifier):
|
|
||||||
conn = get_db_connection()
|
|
||||||
post = conn.execute("SELECT * FROM notes WHERE id = ?",
|
|
||||||
(identifier,)).fetchone()
|
|
||||||
conn.close()
|
|
||||||
if post is None:
|
|
||||||
return None
|
|
||||||
return post
|
|
||||||
|
|
||||||
|
|
||||||
def get_space(identifier):
|
|
||||||
conn = get_db_connection()
|
|
||||||
notes = conn.execute("SELECT content, title FROM notes WHERE creator = ? ORDER BY id DESC;",
|
|
||||||
(identifier,)).fetchall()
|
|
||||||
conn.close()
|
|
||||||
spacetaken = 0
|
|
||||||
for x in notes:
|
|
||||||
spacetaken = spacetaken + len(x["content"].encode("utf-8"))
|
|
||||||
spacetaken = spacetaken + len(x["title"].encode("utf-8"))
|
|
||||||
return spacetaken
|
|
||||||
|
|
||||||
|
|
||||||
def get_note_count(identifier):
|
|
||||||
conn = get_db_connection()
|
|
||||||
notes = conn.execute("SELECT content, title FROM notes WHERE creator = ? ORDER BY id DESC;",
|
|
||||||
(identifier,)).fetchall()
|
|
||||||
conn.close()
|
|
||||||
return len(notes)
|
|
||||||
|
|
||||||
|
|
||||||
def get_session(identifier):
|
|
||||||
conn = get_db_connection()
|
|
||||||
post = conn.execute("SELECT * FROM sessions WHERE session = ?",
|
|
||||||
(identifier,)).fetchone()
|
|
||||||
conn.close()
|
|
||||||
if post is None:
|
|
||||||
return None
|
|
||||||
return post
|
|
||||||
|
|
||||||
def check_username_taken(username):
|
|
||||||
conn = get_db_connection()
|
|
||||||
post = conn.execute("SELECT * FROM users WHERE lower(username) = ?",
|
|
||||||
(username.lower(),)).fetchone()
|
|
||||||
conn.close()
|
|
||||||
if post is None:
|
|
||||||
return None
|
|
||||||
return post["id"]
|
|
||||||
|
|
||||||
|
|
||||||
# Disable CORS
|
|
||||||
@app.after_request
|
|
||||||
async def add_cors_headers(response):
|
|
||||||
response.headers.add("Access-Control-Allow-Origin", "*")
|
|
||||||
response.headers.add("Access-Control-Allow-Headers", "*")
|
|
||||||
response.headers.add("Access-Control-Allow-Methods", "*")
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
# Live editing store
|
|
||||||
messages = {}
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/version", methods=("GET", "POST"))
|
|
||||||
async def apiversion():
|
|
||||||
return "Burgernotes Version 1.2"
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/signup", methods=("GET", "POST"))
|
|
||||||
async def apisignup():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
username = data["username"]
|
|
||||||
password = data["password"]
|
|
||||||
|
|
||||||
if username == "":
|
|
||||||
return {}, 422
|
|
||||||
|
|
||||||
if len(username) > 20:
|
|
||||||
return {}, 422
|
|
||||||
|
|
||||||
if not username.isalnum():
|
|
||||||
return {}, 422
|
|
||||||
|
|
||||||
if password == "":
|
|
||||||
return {}, 422
|
|
||||||
|
|
||||||
if len(password) < 14:
|
|
||||||
return {}, 422
|
|
||||||
|
|
||||||
if not check_username_taken(username) is None:
|
|
||||||
return {}, 409
|
|
||||||
|
|
||||||
hashedpassword = generate_password_hash(password)
|
|
||||||
|
|
||||||
conn = get_db_connection()
|
|
||||||
conn.execute("INSERT INTO users (username, password, created) VALUES (?, ?, ?)",
|
|
||||||
(username, hashedpassword, str(time.time())))
|
|
||||||
|
|
||||||
userID = check_username_taken(username)
|
|
||||||
|
|
||||||
randomCharacters = secrets.token_hex(512)
|
|
||||||
|
|
||||||
conn.execute("INSERT INTO sessions (session, id, device) VALUES (?, ?, ?)",
|
|
||||||
(randomCharacters, userID, request.headers.get("user-agent")))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return {
|
|
||||||
"key": randomCharacters
|
|
||||||
}, 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/login", methods=("GET", "POST"))
|
|
||||||
async def apilogin():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
username = data["username"]
|
|
||||||
password = data["password"]
|
|
||||||
passwordchange = data["passwordchange"]
|
|
||||||
newpass = data["newpass"]
|
|
||||||
|
|
||||||
check_username_thing = check_username_taken(username)
|
|
||||||
|
|
||||||
if check_username_thing is None:
|
|
||||||
return {}, 401
|
|
||||||
|
|
||||||
userID = check_username_taken(username)
|
|
||||||
user = get_user(userID)
|
|
||||||
|
|
||||||
if not check_password_hash(user["password"], password):
|
|
||||||
return {}, 401
|
|
||||||
|
|
||||||
randomCharacters = secrets.token_hex(512)
|
|
||||||
|
|
||||||
conn = get_db_connection()
|
|
||||||
conn.execute("INSERT INTO sessions (session, id, device) VALUES (?, ?, ?)",
|
|
||||||
(randomCharacters, userID, request.headers.get("user-agent")))
|
|
||||||
|
|
||||||
if passwordchange == "yes":
|
|
||||||
hashedpassword = generate_password_hash(newpass)
|
|
||||||
conn.execute("UPDATE users SET password = ? WHERE username = ?", (hashedpassword, username))
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return {
|
|
||||||
"key": randomCharacters,
|
|
||||||
}, 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/userinfo", methods=("GET", "POST"))
|
|
||||||
async def apiuserinfo():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
datatemplate = {
|
|
||||||
"username": user["username"],
|
|
||||||
"id": user["id"],
|
|
||||||
"created": user["created"],
|
|
||||||
"storageused": get_space(user["id"]),
|
|
||||||
"storagemax": MAX_STORAGE,
|
|
||||||
"notecount": get_note_count(user["id"])
|
|
||||||
}
|
|
||||||
return datatemplate
|
|
||||||
|
|
||||||
@app.route("/api/loggedin", methods=("GET", "POST"))
|
|
||||||
async def apiloggedin():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
if get_session(secretKey) != None:
|
|
||||||
return {}, 200
|
|
||||||
else:
|
|
||||||
return {}, 400
|
|
||||||
|
|
||||||
@app.route("/api/listnotes", methods=("GET", "POST"))
|
|
||||||
async def apilistnotes():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
|
|
||||||
conn = get_db_connection()
|
|
||||||
notes = conn.execute("SELECT * FROM notes WHERE creator = ? ORDER BY edited DESC;", (user["id"],)).fetchall()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
datatemplate = []
|
|
||||||
|
|
||||||
for note in notes:
|
|
||||||
notetemplate = {
|
|
||||||
"id": note["id"],
|
|
||||||
"title": note["title"]
|
|
||||||
}
|
|
||||||
datatemplate.append(notetemplate)
|
|
||||||
|
|
||||||
return datatemplate, 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/exportnotes", methods=("GET", "POST"))
|
|
||||||
async def apiexportnotes():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
|
|
||||||
conn = get_db_connection()
|
|
||||||
notes = conn.execute("SELECT * FROM notes WHERE creator = ? ORDER BY id DESC;", (user["id"],)).fetchall()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
datatemplate = []
|
|
||||||
|
|
||||||
for note in notes:
|
|
||||||
notetemplate = {
|
|
||||||
"id": note["id"],
|
|
||||||
"created": note["created"],
|
|
||||||
"edited": note["edited"],
|
|
||||||
"title": note["title"],
|
|
||||||
"content": note["content"]
|
|
||||||
}
|
|
||||||
datatemplate.append(notetemplate)
|
|
||||||
|
|
||||||
return datatemplate, 200
|
|
||||||
|
|
||||||
@app.route("/api/importnotes", methods=("GET", "POST"))
|
|
||||||
async def importnotes():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
notes = data["notes"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
|
|
||||||
conn = get_db_connection()
|
|
||||||
notejson = loads(notes)
|
|
||||||
for note in notejson:
|
|
||||||
conn.execute("INSERT INTO notes (title, content, creator, created, edited) VALUES (?, ?, ?, ?, ?)",
|
|
||||||
(note["title"], note["content"], userCookie["id"], note["created"], note["edited"]))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return {}, 200
|
|
||||||
|
|
||||||
@app.route("/api/newnote", methods=("GET", "POST"))
|
|
||||||
async def apinewnote():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
noteName = data["noteName"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
|
|
||||||
conn = get_db_connection()
|
|
||||||
conn.execute("INSERT INTO notes (title, content, creator, created, edited) VALUES (?, ?, ?, ?, ?)",
|
|
||||||
(noteName, "", user["id"], str(time.time()), str(time.time())))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return {}, 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/readnote", methods=("GET", "POST"))
|
|
||||||
async def apireadnote():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
noteId = data["noteId"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
|
|
||||||
note = get_note(noteId)
|
|
||||||
|
|
||||||
if note is not None:
|
|
||||||
if user["id"] == note["creator"]:
|
|
||||||
contenttemplate = {
|
|
||||||
"content": note["content"]
|
|
||||||
}
|
|
||||||
|
|
||||||
return contenttemplate, 200
|
|
||||||
else:
|
|
||||||
return {}, 422
|
|
||||||
else:
|
|
||||||
return {}, 422
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/waitforedit", methods=("GET", "POST"))
|
|
||||||
async def waitforedit():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
complete = True
|
|
||||||
|
|
||||||
start_time = time.time()
|
|
||||||
while user["id"] not in messages or not messages[user["id"]]:
|
|
||||||
await asyncio.sleep(0)
|
|
||||||
elapsed_time = time.time() - start_time
|
|
||||||
if elapsed_time >= 20:
|
|
||||||
complete = False
|
|
||||||
break
|
|
||||||
|
|
||||||
message = messages[user["id"]].pop(0)
|
|
||||||
del messages[user["id"]]
|
|
||||||
|
|
||||||
if complete:
|
|
||||||
return {
|
|
||||||
"note": message
|
|
||||||
}, 200
|
|
||||||
else:
|
|
||||||
return 400
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/editnote", methods=("GET", "POST"))
|
|
||||||
async def apieditnote():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
noteId = data["noteId"]
|
|
||||||
content = data["content"]
|
|
||||||
title = data["title"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
|
|
||||||
note = get_note(noteId)
|
|
||||||
|
|
||||||
if get_space(user["id"]) + len(content.encode("utf-8")) > int(MAX_STORAGE):
|
|
||||||
return {}, 418
|
|
||||||
|
|
||||||
if note is not None:
|
|
||||||
if user["id"] == note["creator"]:
|
|
||||||
conn = get_db_connection()
|
|
||||||
conn.execute("UPDATE notes SET content = ?, title = ?, edited = ? WHERE id = ?",
|
|
||||||
(content, title, str(time.time()), noteId))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
if user["id"] not in messages:
|
|
||||||
messages[user["id"]] = []
|
|
||||||
messages[user["id"]].append(noteId)
|
|
||||||
|
|
||||||
return {}, 200
|
|
||||||
else:
|
|
||||||
return {}, 403
|
|
||||||
else:
|
|
||||||
return {}, 422
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/editnotetitle", methods=("GET", "POST"))
|
|
||||||
async def apieditnotetitle():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
noteId = data["noteId"]
|
|
||||||
content = data["content"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
|
|
||||||
note = get_note(noteId)
|
|
||||||
|
|
||||||
if get_space(user["id"]) + len(content.encode("utf-8")) > int(MAX_STORAGE):
|
|
||||||
return {}, 418
|
|
||||||
|
|
||||||
if note is not None:
|
|
||||||
if user["id"] == note["creator"]:
|
|
||||||
conn = get_db_connection()
|
|
||||||
conn.execute("UPDATE notes SET title = ?, edited = ? WHERE id = ?", (content, str(time.time()), noteId))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return {}, 200
|
|
||||||
else:
|
|
||||||
return {}, 403
|
|
||||||
else:
|
|
||||||
return {}, 422
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/removenote", methods=("GET", "POST"))
|
|
||||||
async def apiremovenote():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
noteId = data["noteId"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
|
|
||||||
note = get_note(noteId)
|
|
||||||
|
|
||||||
if note is not None:
|
|
||||||
if user["id"] == note["creator"]:
|
|
||||||
conn = get_db_connection()
|
|
||||||
conn.execute("DELETE FROM notes WHERE id = ?", (noteId,))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return {}, 200
|
|
||||||
else:
|
|
||||||
return {}, 403
|
|
||||||
else:
|
|
||||||
return {}, 422
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/deleteaccount", methods=("GET", "POST"))
|
|
||||||
async def apideleteaccount():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
|
|
||||||
conn = get_db_connection()
|
|
||||||
conn.execute("DELETE FROM notes WHERE creator = ?", (userCookie["id"],))
|
|
||||||
conn.execute("DELETE FROM users WHERE id = ?", (userCookie["id"],))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return {}, 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/sessions/list", methods=("GET", "POST"))
|
|
||||||
async def apisessionslist():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
|
|
||||||
conn = get_db_connection()
|
|
||||||
sessions = conn.execute("SELECT * FROM sessions WHERE id = ? ORDER BY id DESC;", (user["id"],)).fetchall()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
datatemplate = []
|
|
||||||
|
|
||||||
for x in sessions:
|
|
||||||
device = x["device"]
|
|
||||||
thisSession = False
|
|
||||||
if x["session"] == secretKey:
|
|
||||||
thisSession = True
|
|
||||||
sessiontemplate = {
|
|
||||||
"id": x["sessionid"],
|
|
||||||
"thisSession": thisSession,
|
|
||||||
"device": device
|
|
||||||
}
|
|
||||||
datatemplate.append(sessiontemplate)
|
|
||||||
|
|
||||||
return datatemplate, 200
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/sessions/remove", methods=("GET", "POST"))
|
|
||||||
async def apisessionsremove():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
secretKey = data["secretKey"]
|
|
||||||
sessionId = data["sessionId"]
|
|
||||||
|
|
||||||
userCookie = get_session(secretKey)
|
|
||||||
user = get_user(userCookie["id"])
|
|
||||||
|
|
||||||
if userCookie is not None:
|
|
||||||
if user["id"] == userCookie["id"]:
|
|
||||||
conn = get_db_connection()
|
|
||||||
conn.execute("DELETE FROM sessions WHERE sessionid = ?", (sessionId,))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
return {}, 200
|
|
||||||
else:
|
|
||||||
return {}, 403
|
|
||||||
else:
|
|
||||||
return {}, 422
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/listusers", methods=("GET", "POST"))
|
|
||||||
async def listusers():
|
|
||||||
if request.method == "POST":
|
|
||||||
data = await request.get_json()
|
|
||||||
masterkey = data["masterkey"]
|
|
||||||
if masterkey == SECRET_KEY:
|
|
||||||
conn = get_db_connection()
|
|
||||||
users = conn.execute("SELECT * FROM users").fetchall()
|
|
||||||
conn.close()
|
|
||||||
thing = []
|
|
||||||
for x in users:
|
|
||||||
user_info = {
|
|
||||||
"id": str(x["id"]),
|
|
||||||
"username": str(x["username"]),
|
|
||||||
"space": str(get_space(x["id"]))
|
|
||||||
}
|
|
||||||
thing.append(user_info)
|
|
||||||
return thing, 200
|
|
||||||
else:
|
|
||||||
return {}, 401
|
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(500)
|
|
||||||
async def burger():
|
|
||||||
return {}, 500
|
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
|
||||||
async def burger():
|
|
||||||
return {}, 404
|
|
||||||
|
|
||||||
|
|
||||||
# Start server
|
|
||||||
hypercornconfig = Config()
|
|
||||||
hypercornconfig.bind = (HOST + ":" + PORT)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print("[INFO] Server started at", str(int(time.time())))
|
|
||||||
print("[INFO] Welcome to Burgernotes! Today we are running on IP " + HOST + " on port " + PORT + ".")
|
|
||||||
asyncio.run(serve(app, hypercornconfig))
|
|
|
@ -0,0 +1,984 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-contrib/sessions"
|
||||||
|
"github.com/gin-contrib/sessions/cookie"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
_ "github.com/mattn/go-sqlite3"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"golang.org/x/crypto/scrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
conn *sql.DB
|
||||||
|
host string
|
||||||
|
port int
|
||||||
|
secretKey string
|
||||||
|
maxStorage int64
|
||||||
|
saltChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||||
|
)
|
||||||
|
|
||||||
|
func genSalt(length int) (string, error) {
|
||||||
|
if length <= 0 {
|
||||||
|
log.Println("[ERROR] Known in genSalt() at", strconv.FormatInt(time.Now().Unix(), 10)+":", "Salt length must be at least one.")
|
||||||
|
}
|
||||||
|
|
||||||
|
salt := make([]byte, length)
|
||||||
|
randomBytes := make([]byte, length)
|
||||||
|
_, err := rand.Read(randomBytes)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in genSalt() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range salt {
|
||||||
|
salt[i] = saltChars[int(randomBytes[i])%len(saltChars)]
|
||||||
|
}
|
||||||
|
return string(salt), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func hash(password, salt string) (string, error) {
|
||||||
|
passwordBytes := []byte(password)
|
||||||
|
saltBytes := []byte(salt)
|
||||||
|
|
||||||
|
derivedKey, err := scrypt.Key(passwordBytes, saltBytes, 32768, 8, 1, 64)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in hash() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
hashString := fmt.Sprintf("scrypt:32768:8:1$%s$%s", salt, hex.EncodeToString(derivedKey))
|
||||||
|
return hashString, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyHash(werkzeugHash, password string) (bool, error) {
|
||||||
|
parts := strings.Split(werkzeugHash, "$")
|
||||||
|
if len(parts) != 3 || parts[0] != "scrypt:32768:8:1" {
|
||||||
|
return false, errors.New("invalid hash format")
|
||||||
|
}
|
||||||
|
salt := parts[1]
|
||||||
|
|
||||||
|
computedHash, err := hash(password, salt)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return werkzeugHash == computedHash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUser(id int) (string, string, string, error) {
|
||||||
|
var created, username, password string
|
||||||
|
err := conn.QueryRow("SELECT created, username, password FROM users WHERE id = ? LIMIT 1", id).Scan(&created, &username, &password)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return created, username, password, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNote(id int) (int, string, string, string, string, error) {
|
||||||
|
var creator int
|
||||||
|
var created, edited, content, title string
|
||||||
|
err := conn.QueryRow("SELECT creator, created, edited, content, title FROM notes WHERE id = ? LIMIT 1", id).Scan(&creator, &created, &edited, &content, &title)
|
||||||
|
if err != nil {
|
||||||
|
return 0, "", "", "", "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return creator, created, edited, content, title, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSpace(id int) (int, error) {
|
||||||
|
var space int
|
||||||
|
err := conn.QueryRow("SELECT COALESCE(SUM(LENGTH(content) + LENGTH(title)), 0) FROM notes WHERE creator = ?", id).Scan(&space)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return space, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNoteCount(id int) (int, error) {
|
||||||
|
var count int
|
||||||
|
err := conn.QueryRow("SELECT COUNT(*) FROM notes WHERE creator = ?", id).Scan(&count)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkUsernameTaken(username string) (int, bool, error) {
|
||||||
|
var id int
|
||||||
|
err := conn.QueryRow("SELECT id FROM users WHERE lower(username) = ? LIMIT 1", strings.ToLower(username)).Scan(&id)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return 0, false, nil
|
||||||
|
} else {
|
||||||
|
return 0, true, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return id, true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSession(session string) (int, int, error) {
|
||||||
|
var id int
|
||||||
|
var sessionId int
|
||||||
|
err := conn.QueryRow("SELECT sessionid, id FROM sessions WHERE session = ? LIMIT 1", session).Scan(&sessionId, &id)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return sessionId, id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSessionFromId(sessionId int) (string, int, error) {
|
||||||
|
var id int
|
||||||
|
var session string
|
||||||
|
err := conn.QueryRow("SELECT session, id FROM sessions WHERE sessionid = ? LIMIT 1", sessionId).Scan(&session, &id)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
return session, id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func generateDB() error {
|
||||||
|
schemaBytes, err := os.ReadFile("schema.sql")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = conn.Exec(string(schemaBytes))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Println("[INFO] Generated database")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initDb() {
|
||||||
|
_, err := os.Stat("database.db")
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = generateDB()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("[FATAL] Unknown while generating database at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Print("[PROMPT] Proceeding will overwrite the database. Proceed? (y/n): ")
|
||||||
|
var answer string
|
||||||
|
_, err := fmt.Scanln(&answer)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("[FATAL] Unknown while scanning input at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
}
|
||||||
|
if strings.ToLower(answer) == "y" {
|
||||||
|
err := generateDB()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("[FATAL] Unknown while generating database at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else if answer == ":3" {
|
||||||
|
log.Println("[:3] :3")
|
||||||
|
} else {
|
||||||
|
log.Println("[INFO] Stopped")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if _, err := os.Stat("config.ini"); err == nil {
|
||||||
|
log.Println("[INFO] Config loaded at", time.Now().Unix())
|
||||||
|
} else if os.IsNotExist(err) {
|
||||||
|
log.Fatalln("[FATAL] config.ini does not exist")
|
||||||
|
} else {
|
||||||
|
log.Fatalln("[FATAL] File is in quantum uncertainty:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
viper.SetConfigName("config")
|
||||||
|
viper.AddConfigPath("./")
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
|
err := viper.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("[FATAL] Error in config file at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
host = viper.GetString("config.HOST")
|
||||||
|
port = viper.GetInt("config.PORT")
|
||||||
|
secretKey = viper.GetString("config.SECRET_KEY")
|
||||||
|
maxStorage = viper.GetInt64("config.MAX_STORAGE")
|
||||||
|
|
||||||
|
if host == "" {
|
||||||
|
log.Fatalln("[FATAL] HOST is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if port == 0 {
|
||||||
|
log.Fatalln("[FATAL] PORT is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if secretKey == "" {
|
||||||
|
log.Fatalln("[FATAL] SECRET_KEY is not set")
|
||||||
|
} else if secretKey == "supersecretkey" {
|
||||||
|
log.Println("[WARN] SECRET_KEY is set to a default value. Please set it to another value.")
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxStorage == 0 {
|
||||||
|
log.Fatalln("[FATAL] MAX_STORAGE is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err = sql.Open("sqlite3", "database.db")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("[FATAL] Cannot open database at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
}
|
||||||
|
defer func(conn *sql.DB) {
|
||||||
|
err := conn.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in main() defer at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
}
|
||||||
|
}(conn)
|
||||||
|
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
if os.Args[1] == "init_db" {
|
||||||
|
initDb()
|
||||||
|
os.Exit(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gin.SetMode(gin.ReleaseMode)
|
||||||
|
router := gin.New()
|
||||||
|
store := cookie.NewStore([]byte(secretKey))
|
||||||
|
router.Use(sessions.Sessions("session", store))
|
||||||
|
|
||||||
|
router.Use(func(c *gin.Context) {
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Headers", "*, Authorization")
|
||||||
|
c.Writer.Header().Set("Access-Control-Allow-Methods", "*")
|
||||||
|
|
||||||
|
if c.Request.Method == "OPTIONS" {
|
||||||
|
c.AbortWithStatus(200)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
})
|
||||||
|
|
||||||
|
router.GET("/api/version", func(c *gin.Context) {
|
||||||
|
c.String(200, "Burgernotes Version 2.0 Beta 1")
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/signup", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := data["username"].(string)
|
||||||
|
password := data["password"].(string)
|
||||||
|
|
||||||
|
if username == "" || password == "" || len(username) > 20 || !regexp.MustCompile("^[a-zA-Z0-9]+$").MatchString(username) {
|
||||||
|
c.JSON(422, gin.H{"error": "Invalid username or password"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, taken, err := checkUsernameTaken(username)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/signup checkUsernameTaken() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SIGNUP-USERTAKEN"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if taken {
|
||||||
|
c.JSON(409, gin.H{"error": "Username is taken"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
salt, err := genSalt(16)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/signup genSalt() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SIGNUP-SALT"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hashedPasswd, err := hash(password, salt)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/signup hash() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SIGNUP-HASH"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Exec("INSERT INTO users (username, password, created) VALUES (?, ?, ?)", username, hashedPasswd, strconv.FormatInt(time.Now().Unix(), 10))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/signup Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SIGNUP-DBINSERT"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("[INFO] Added new user at", time.Now().Unix())
|
||||||
|
userid, taken, err := checkUsernameTaken(username)
|
||||||
|
if !taken {
|
||||||
|
log.Println("[CRITICAL] Something is very wrong! A user was created but could not be found in the database at", time.Now().Unix())
|
||||||
|
log.Println("[INFO] This should not be possible. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes with the error code: UNKNOWN-API-SIGNUP-POSTTAKEN")
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SIGNUP-POSTTAKEN"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := genSalt(512)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/signup token genSalt() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SIGNUP-SESSIONSALT"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, err = conn.Exec("INSERT INTO sessions (session, id, device) VALUES (?, ?, ?)", token, userid, c.Request.Header.Get("User-Agent"))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/signup session Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SIGNUP-SESSIONINSERT"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"key": token})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/login", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := data["username"].(string)
|
||||||
|
password := data["password"].(string)
|
||||||
|
|
||||||
|
userid, taken, err := checkUsernameTaken(username)
|
||||||
|
if !taken {
|
||||||
|
c.JSON(401, gin.H{"error": "User does not exist"})
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/login checkUsernameTaken() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LOGIN-USERTAKEN"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, hashedPasswd, err := getUser(userid)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/login getUser() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LOGIN-GETUSER"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
correctPassword, err := verifyHash(hashedPasswd, password)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, errors.New("invalid hash format")) {
|
||||||
|
c.JSON(500, gin.H{"error": "Invalid hash format"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Println("[ERROR] Unknown in /api/login verifyHash() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LOGIN-VERIFYHASH"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !correctPassword {
|
||||||
|
c.JSON(401, gin.H{"error": "Incorrect password"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token, err := genSalt(512)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/login token genSalt() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LOGIN-SESSIONSALT"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Exec("INSERT INTO sessions (session, id, device) VALUES (?, ?, ?)", token, userid, c.Request.Header.Get("User-Agent"))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/login session Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LOGIN-SESSIONINSERT"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"key": token})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/userinfo", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
created, username, _, err := getUser(userid)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/userinfo getUser() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-USERINFO-GETUSER"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
space, err := getSpace(userid)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/userinfo getSpace() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-USERINFO-GETSPACE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
notecount, err := getNoteCount(userid)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/userinfo getNoteCount() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-USERINFO-GETNOTECOUNT"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c.JSON(200, gin.H{"username": username, "id": userid, "created": created, "storageused": space, "storagemax": maxStorage, "notecount": notecount})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/loggedin", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if userid > 0 {
|
||||||
|
c.JSON(200, gin.H{"loggedin": true})
|
||||||
|
} else {
|
||||||
|
c.JSON(403, gin.H{"loggedin": false})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/listnotes", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := conn.Query("SELECT id, title FROM notes WHERE creator = ? ORDER BY edited DESC", userid)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
c.JSON(200, []map[string]interface{}{})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Println("[ERROR] Unknown in /api/listnotes query at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LISTNOTES-DBQUERY"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func(rows *sql.Rows) {
|
||||||
|
err := rows.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/listnotes row defer at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LISTNOTES-ROWCLOSE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(rows)
|
||||||
|
|
||||||
|
var notes []map[string]interface{}
|
||||||
|
for rows.Next() {
|
||||||
|
var id int
|
||||||
|
var title string
|
||||||
|
if err := rows.Scan(&id, &title); err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/listnotes row scan at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LISTNOTES-ROWSCAN"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notes = append(notes, map[string]interface{}{"id": id, "title": title})
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/listnotes row iteration at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LISTNOTES-ROWERR"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, notes)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/exportnotes", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := conn.Query("SELECT id, created, edited, title, content FROM notes WHERE creator = ? ORDER BY edited DESC", userid)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
c.JSON(200, []map[string]interface{}{})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Println("[ERROR] Unknown in /api/exportnotes query at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-EXPORTNOTES-DBQUERY"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func(rows *sql.Rows) {
|
||||||
|
err := rows.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/exportnotes row defer at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-EXPORTNOTES-ROWCLOSE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(rows)
|
||||||
|
|
||||||
|
var notes []map[string]interface{}
|
||||||
|
for rows.Next() {
|
||||||
|
var id int
|
||||||
|
var created, edited, title, content string
|
||||||
|
if err := rows.Scan(&id, &created, &edited, &title, &content); err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/exportnotes row scan at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-EXPORTNOTES-ROWSCAN"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notes = append(notes, map[string]interface{}{"id": id, "created": created, "edited": edited, "title": title, "content": content})
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/exportnotes row iteration at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-EXPORTNOTES-ROWERR"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, notes)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/importnotes", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
notesStr := data["notes"].(string)
|
||||||
|
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var notes []interface{}
|
||||||
|
err = json.Unmarshal([]byte(notesStr), ¬es)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, note := range notes {
|
||||||
|
note := note.(map[string]interface{})
|
||||||
|
_, err := conn.Exec("INSERT INTO notes (creator, created, edited, title, content) VALUES (?, ?, ?, ?, ?)", userid, note["created"], note["edited"], note["title"], note["content"])
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/importnotes Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-IMPORTNOTES-DBINSERT"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"success": true})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/newnote", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
noteName := data["noteName"].(string)
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
space, err := getSpace(userid)
|
||||||
|
if int64(len(noteName)+space) > maxStorage {
|
||||||
|
c.JSON(403, gin.H{"error": "Storage limit reached"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
_, err := conn.Exec("INSERT INTO notes (title, content, creator, created, edited) VALUES (?, ?, ?, ?, ?)", noteName, "", userid, strconv.FormatInt(time.Now().Unix(), 10), strconv.FormatInt(time.Now().Unix(), 10))
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/newnote Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-NEWNOTE-DBINSERT"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{"success": true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/readnote", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
noteId := int(data["noteId"].(float64))
|
||||||
|
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
creator, _, _, content, _, err := getNote(noteId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
c.JSON(422, gin.H{"error": "Note not found"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Println("[ERROR] Unknown in /api/readnote getNote() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-READNOTE-GETNOTE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if creator != userid {
|
||||||
|
c.JSON(422, gin.H{"error": "Note does not belong to user"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{"content": content})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/editnote", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
noteId := int(data["noteId"].(float64))
|
||||||
|
content := data["content"].(string)
|
||||||
|
title := data["title"].(string)
|
||||||
|
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
creator, _, _, _, _, err := getNote(noteId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
c.JSON(422, gin.H{"error": "Note not found"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Println("[ERROR] Unknown in /api/editnote getNote() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-EDITNOTE-GETNOTE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if creator != userid {
|
||||||
|
c.JSON(403, gin.H{"error": "Note does not belong to user"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
_, err := conn.Exec("UPDATE notes SET content = ?, title = ?, edited = ? WHERE id = ?", content, title, strconv.FormatInt(time.Now().Unix(), 10), noteId)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/editnote Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-EDITNOTE-DBUPDATE"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{"success": true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/removenote", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
noteId := int(data["noteId"].(float64))
|
||||||
|
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
creator, _, _, _, _, err := getNote(noteId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
c.JSON(422, gin.H{"error": "Note not found"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Println("[ERROR] Unknown in /api/removenote getNote() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-REMOVENOTE-GETNOTE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if creator != userid {
|
||||||
|
c.JSON(403, gin.H{"error": "Note does not belong to user"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
_, err := conn.Exec("DELETE FROM notes WHERE id = ?", noteId)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/removenote Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-REMOVENOTE-DBDELETE"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{"success": true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/deleteaccount", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Exec("DELETE FROM notes WHERE creator = ?", userid)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/deleteaccount notes Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-DELETEACCOUNT-NOTESDELETE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Exec("DELETE FROM users WHERE id = ?", userid)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/deleteaccount user Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-DELETEACCOUNT-USERDELETE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = conn.Exec("DELETE FROM sessions WHERE id = ?", userid)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/deleteaccount session Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-DELETEACCOUNT-SESSIONDELETE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"success": true})
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/sessions/list", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := conn.Query("SELECT sessionid, session, device FROM sessions WHERE id = ? ORDER BY id DESC", userid)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
c.JSON(200, []map[string]interface{}{})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Println("[ERROR] Unknown in /api/sessions/list query at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SESSIONS-LIST-DBQUERY"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func(rows *sql.Rows) {
|
||||||
|
err := rows.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/sessions/list row defer at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SESSIONS-LIST-ROWCLOSE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(rows)
|
||||||
|
|
||||||
|
var sessionList []map[string]interface{}
|
||||||
|
for rows.Next() {
|
||||||
|
var sessionid int
|
||||||
|
var session, device string
|
||||||
|
if err := rows.Scan(&sessionid, &session, &device); err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/sessions/list row scan at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SESSIONS-LIST-ROWSCAN"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if session == token {
|
||||||
|
sessionList = append(sessionList, map[string]interface{}{"id": sessionid, "thisSession": true, "device": device})
|
||||||
|
} else {
|
||||||
|
sessionList = append(sessionList, map[string]interface{}{"id": sessionid, "thisSession": false, "device": device})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/sessions/list row iteration at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SESSIONS-LIST-ROWERR"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, sessionList)
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/sessions/remove", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
token := data["secretKey"].(string)
|
||||||
|
sessionId := int(data["sessionId"].(float64))
|
||||||
|
|
||||||
|
_, userid, err := getSession(token)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(401, gin.H{"error": "Invalid session"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, creator, err := getSessionFromId(sessionId)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
c.JSON(422, gin.H{"error": "Target session not found"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Println("[ERROR] Unknown in /api/sessions/remove getSession() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SESSIONS-REMOVE-GETSESSION"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if creator != userid {
|
||||||
|
c.JSON(403, gin.H{"error": "Session does not belong to user"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
_, err := conn.Exec("DELETE FROM sessions WHERE sessionid = ?", sessionId)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/sessions/remove Exec() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-SESSIONS-REMOVE-DBDELETE"})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
c.JSON(200, gin.H{"success": true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
router.POST("/api/listusers", func(c *gin.Context) {
|
||||||
|
var data map[string]interface{}
|
||||||
|
err := c.ShouldBindJSON(&data)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
masterToken := data["masterkey"].(string)
|
||||||
|
if masterToken == secretKey {
|
||||||
|
rows, err := conn.Query("SELECT id, username, created FROM users ORDER BY id DESC")
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
c.JSON(200, []map[string]interface{}{})
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
log.Println("[ERROR] Unknown in /api/listusers query at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LISTUSERS-DBQUERY"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer func(rows *sql.Rows) {
|
||||||
|
err := rows.Close()
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/listusers row defer at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LISTUSERS-ROWCLOSE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}(rows)
|
||||||
|
|
||||||
|
var users []map[string]interface{}
|
||||||
|
for rows.Next() {
|
||||||
|
var id int
|
||||||
|
var username, created string
|
||||||
|
if err := rows.Scan(&id, &username, &created); err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/listusers row scan at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LISTUSERS-ROWSCAN"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
space, err := getSpace(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/listusers getSpace() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LISTUSERS-GETSPACE"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
notes, err := getNoteCount(id)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/listusers getNoteCount() at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
c.JSON(500, gin.H{"error": "Something went wrong on our end. Please report this bug at https://centrifuge.hectabit.org/hectabit/burgernotes and refer to the documentation for more info. Your error code is: UNKNOWN-API-LISTUSERS-GETNOTECOUNT"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
users = append(users, map[string]interface{}{"id": id, "username": username, "created": created, "space": space, "notes": notes})
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
log.Println("[ERROR] Unknown in /api/listusers row iteration at", strconv.FormatInt(time.Now().Unix(), 10)+":", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Println("[INFO] Server started at", time.Now().Unix())
|
||||||
|
log.Println("[INFO] Welcome to Burgernotes! Today we are running on IP " + host + " on port " + strconv.Itoa(port) + ".")
|
||||||
|
err = router.Run(host + ":" + strconv.Itoa(port))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("[FATAL] Server failed to begin operations at", time.Now().Unix(), err)
|
||||||
|
}
|
||||||
|
}
|
32
removeuser
32
removeuser
|
@ -1,32 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
import sqlite3
|
|
||||||
import sys
|
|
||||||
|
|
||||||
print("type n to cancel")
|
|
||||||
answer = input("delete user, what is user id?")
|
|
||||||
|
|
||||||
if answer == "n":
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
|
|
||||||
def get_db_connection():
|
|
||||||
connection = sqlite3.connect("database.db")
|
|
||||||
connection.row_factory = sqlite3.Row
|
|
||||||
return connection
|
|
||||||
|
|
||||||
|
|
||||||
print("deleting notes")
|
|
||||||
|
|
||||||
conn = get_db_connection()
|
|
||||||
notes = conn.execute("DELETE FROM notes WHERE creator = ?", (int(answer),))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
print("deleting account")
|
|
||||||
|
|
||||||
conn = get_db_connection()
|
|
||||||
conn.execute("DELETE FROM users WHERE id = ?", (int(answer),))
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
print("success")
|
|
|
@ -1,3 +0,0 @@
|
||||||
quart
|
|
||||||
hypercorn
|
|
||||||
werkzeug
|
|
18
vaccum
18
vaccum
|
@ -1,18 +0,0 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
|
|
||||||
def get_db_connection():
|
|
||||||
conn = sqlite3.connect("database.db")
|
|
||||||
conn.row_factory = sqlite3.Row
|
|
||||||
return conn
|
|
||||||
|
|
||||||
if os.path.exists("database.db"):
|
|
||||||
print("[INFO] Vacuuming...")
|
|
||||||
conn = get_db_connection()
|
|
||||||
conn.execute("VACUUM")
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
print("[INFO] Success")
|
|
||||||
else:
|
|
||||||
print("[ERROR] Database not found")
|
|
Loading…
Reference in New Issue