Compare commits

...
This repository has been archived on 2019-02-15. You can view files and clone it, but cannot push or open issues or pull requests.

36 Commits

Author SHA1 Message Date
Derek 89bfd8756c
Remove unsed template 2019-02-15 11:59:08 -07:00
Derek 02ea66c2b1
Fix button hide when not in browse page 2019-02-15 11:58:36 -07:00
Derek 650421695c
Return 404 if trying to upload to non-existant directory 2019-02-15 11:46:45 -07:00
Derek 00482e29f2
Fix small bugs in upload page 2018-09-12 16:38:52 -07:00
Derek 25ca7bb9bd
*snort* 2018-09-12 13:08:31 -07:00
Derek 1bb3e59a46
Update readme for bower deps 2018-09-08 17:30:50 -07:00
Derek f2f215acda
Use abspath over realpath
Symbolic links are handy dang it
2018-09-08 17:30:18 -07:00
Derek 7e507748c4
Remove extra padding on login_form
Ok this one was my fault
2018-09-08 17:24:13 -07:00
Derek ce29702f50
Remove extra padding on referal form
Heckin chrome devtools
2018-09-08 17:18:05 -07:00
Derek c549e1c942
Fix urlencoded folder walk vulnerability + code cleanup
+ Check paths before joining them
+ use abort rather than early returns with error templates
2018-09-08 17:16:41 -07:00
Derek 93d6c235f5
Implemented upload page
And all the polymer bits that come with that
2018-09-08 17:12:07 -07:00
Derek 4d7a81c0c9
Replace placeholder headerbar text 2018-09-08 13:42:20 -07:00
Derek 12f086c650
Fix bad security bug
Make sure user actually has the premissions to grant these things
2018-09-08 13:39:22 -07:00
Derek 680e52a6ab
Fix bug with 403's causing inifite redirect loops 2018-09-08 13:03:17 -07:00
Derek d8a9565dc3
Misc improvements
1. Customise login_required and needs_refresh messages
2. Add titles (hover text) to buttons
3. Fade out flashed messages (in case they overflow the screen)
2018-09-08 12:51:04 -07:00
Derek 8add5cefda
Fixed bug where referals didnt carry user args over 2018-09-08 12:48:50 -07:00
Derek 58bd4895ce
Implemented referal creation flow 2018-09-08 12:48:10 -07:00
Derek e4b0f4cff8
Added new user permissions into db
can_refer: Can create referal codes
can_upload: Can upload new files
can_manage: Can perform administrative duties

All false by default except for user 'admin'
2018-09-08 10:55:46 -07:00
Derek c001137094
Add eventlet as a dependency
You're not going to want to do shareing of large files
with synchronous workers, timeouts galore
2018-08-24 13:24:26 -07:00
Derek 838637de55
Fix "SERVE_DIR" config 2018-08-24 13:22:44 -07:00
Derek aa0921aae6
Fix redirects when hosted under non-root 2018-08-24 13:22:08 -07:00
Derek 333dbf9faa
Implement basic ratelimiting 2018-07-26 14:30:23 -07:00
Derek d729ab8ef0
Fix various css bugs
Fixes
+ Responsive layout down to 320px width
+ waifu drawing over file list
+ filename wrapping
2018-07-26 13:45:19 -07:00
Derek d68be11789
Add a readme 2018-07-26 12:22:40 -07:00
Derek b793963d8f
Add setup script for easier first-time deployment 2018-07-26 12:21:48 -07:00
Derek 76509445ff
Use config.json instead of settings.cfg 2018-07-26 12:17:49 -07:00
Derek c7a9639a29
Change referal key from uuid to urlsafe token via secrets module
Also, added compare_type=True to migration env
to catch changes like this in the future

Other than that the title says it all buddy boi
2018-06-03 16:34:02 -07:00
Derek e90f6fb215
Add responsive button / header bar 2018-06-03 16:34:00 -07:00
Derek 3e5cb113a7
Use web-font for icons rather than svgs 2018-06-03 16:33:58 -07:00
Derek cee989e772
Slighly improve #waifu layout hack
still a hack, but
2018-06-03 16:33:56 -07:00
Derek edbf1bff5c
Add user signup flow via user referal
gonna need a default user for this to work
2018-06-03 16:33:55 -07:00
Derek 83b386390f
Fix css layout and text bugs 2018-06-03 16:33:54 -07:00
Derek 5662757981
Implement flashes
vastly improves UX bc previously the user would
just have no idea what the hell was going on lol
2018-06-03 16:33:52 -07:00
Derek 3a32127ba2
User login / logout flow 2018-06-03 16:33:51 -07:00
Derek 6a63b32f9b
Add user model
settings are no longer tracked due to containing a secret key.
get it from the env maybe?
2018-06-03 16:33:47 -07:00
Derek 555f4f57e9
Add db capibilities
We can store and retrieve stuff now. Nice
2018-06-03 16:33:43 -07:00
33 changed files with 1684 additions and 89 deletions

3
.bowerrc Normal file
View File

@ -0,0 +1,3 @@
{
"directory": "static/components"
}

3
.gitignore vendored
View File

@ -1 +1,4 @@
__pycache__/
*.db
config.json
static/components/

View File

@ -6,6 +6,13 @@ name = "pypi"
[packages]
flask = "*"
gunicorn = "*"
flask-login = "*"
flask-sqlalchemy = "*"
bcrypt = "*"
flask-migrate = "*"
click = "*"
flask-limiter = "*"
eventlet = "*"
[dev-packages]

216
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "81cb5d5f0b11719d8d9c5ec9cc683fdcf959c652fda256d5552a82d0f459a99c"
"sha256": "2e5352c6c0be1150d27c750324f7c132acf21f9bb4c9dcd04c8370c96761400a"
},
"pipfile-spec": 6,
"requires": {
@ -16,13 +16,113 @@
]
},
"default": {
"alembic": {
"hashes": [
"sha256:52d73b1d750f1414fa90c25a08da47b87de1e4ad883935718a8f36396e19e78e",
"sha256:eb7db9b4510562ec37c91d00b00d95fde076c1030d3f661aea882eec532b3565"
],
"version": "==1.0.0"
},
"bcrypt": {
"hashes": [
"sha256:01477981abf74e306e8ee31629a940a5e9138de000c6b0898f7f850461c4a0a5",
"sha256:054d6e0acaea429e6da3613fcd12d05ee29a531794d96f6ab959f29a39f33391",
"sha256:0872eeecdf9a429c1420158500eedb323a132bc5bf3339475151c52414729e70",
"sha256:09a3b8c258b815eadb611bad04ca15ec77d86aa9ce56070e1af0d5932f17642a",
"sha256:0f317e4ffbdd15c3c0f8ab5fbd86aa9aabc7bea18b5cc5951b456fe39e9f738c",
"sha256:2788c32673a2ad0062bea850ab73cffc0dba874db10d7a3682b6f2f280553f20",
"sha256:321d4d48be25b8d77594d8324c0585c80ae91ac214f62db9098734e5e7fb280f",
"sha256:346d6f84ff0b493dbc90c6b77136df83e81f903f0b95525ee80e5e6d5e4eef84",
"sha256:34dd60b90b0f6de94a89e71fcd19913a30e83091c8468d0923a93a0cccbfbbff",
"sha256:3b4c23300c4eded8895442c003ae9b14328ae69309ac5867e7530de8bdd7875d",
"sha256:43d1960e7db14042319c46925892d5fa99b08ff21d57482e6f5328a1aca03588",
"sha256:49e96267cd9be55a349fd74f9852eb9ae2c427cd7f6455d0f1765d7332292832",
"sha256:63e06ffdaf4054a89757a3a1ab07f1b922daf911743114a54f7c561b9e1baa58",
"sha256:67ed1a374c9155ec0840214ce804616de49c3df9c5bc66740687c1c9b1cd9e8d",
"sha256:6b662a5669186439f4f583636c8d6ea77cf92f7cfe6aae8d22edf16c36840574",
"sha256:6efd9ca20aefbaf2e7e6817a2c6ed4a50ff6900fafdea1bcb1d0e9471743b144",
"sha256:8569844a5d8e1fdde4d7712a05ab2e6061343ac34af6e7e3d7935b2bd1907bfd",
"sha256:8629ea6a8a59f865add1d6a87464c3c676e60101b8d16ef404d0a031424a8491",
"sha256:988cac675e25133d01a78f2286189c1f01974470817a33eaf4cfee573cfb72a5",
"sha256:9a6fedda73aba1568962f7543a1f586051c54febbc74e87769bad6a4b8587c39",
"sha256:9eced8962ce3b7124fe20fd358cf8c7470706437fa064b9874f849ad4c5866fc",
"sha256:a005ed6163490988711ff732386b08effcbf8df62ae93dd1e5bda0714fad8afb",
"sha256:ae35dbcb6b011af6c840893b32399252d81ff57d52c13e12422e16b5fea1d0fb",
"sha256:b1e8491c6740f21b37cca77bc64677696a3fb9f32360794d57fa8477b7329eda",
"sha256:c906bdb482162e9ef48eea9f8c0d967acceb5c84f2d25574c7d2a58d04861df1",
"sha256:cb18ffdc861dbb244f14be32c47ab69604d0aca415bee53485fcea4f8e93d5ef",
"sha256:cc2f24dc1c6c88c56248e93f28d439ee4018338567b0bbb490ea26a381a29b1e",
"sha256:d860c7fff18d49e20339fc6dffc2d485635e36d4b2cccf58f45db815b64100b4",
"sha256:d86da365dda59010ba0d1ac45aa78390f56bf7f992e65f70b3b081d5e5257b09",
"sha256:e22f0997622e1ceec834fd25947dc2ee2962c2133ea693d61805bc867abaf7ea",
"sha256:f2fe545d27a619a552396533cddf70d83cecd880a611cdfdbb87ca6aec52f66b",
"sha256:f425e925485b3be48051f913dbe17e08e8c48588fdf44a26b8b14067041c0da6",
"sha256:f7fd3ed3745fe6e81e28dc3b3d76cce31525a91f32a387e1febd6b982caf8cdb",
"sha256:f9210820ee4818d84658ed7df16a7f30c9fba7d8b139959950acef91745cc0f7"
],
"index": "pypi",
"version": "==3.1.4"
},
"cffi": {
"hashes": [
"sha256:151b7eefd035c56b2b2e1eb9963c90c6302dc15fbd8c1c0a83a163ff2c7d7743",
"sha256:1553d1e99f035ace1c0544050622b7bc963374a00c467edafac50ad7bd276aef",
"sha256:1b0493c091a1898f1136e3f4f991a784437fac3673780ff9de3bcf46c80b6b50",
"sha256:2ba8a45822b7aee805ab49abfe7eec16b90587f7f26df20c71dd89e45a97076f",
"sha256:3bb6bd7266598f318063e584378b8e27c67de998a43362e8fce664c54ee52d30",
"sha256:3c85641778460581c42924384f5e68076d724ceac0f267d66c757f7535069c93",
"sha256:3eb6434197633b7748cea30bf0ba9f66727cdce45117a712b29a443943733257",
"sha256:495c5c2d43bf6cebe0178eb3e88f9c4aa48d8934aa6e3cddb865c058da76756b",
"sha256:4c91af6e967c2015729d3e69c2e51d92f9898c330d6a851bf8f121236f3defd3",
"sha256:57b2533356cb2d8fac1555815929f7f5f14d68ac77b085d2326b571310f34f6e",
"sha256:770f3782b31f50b68627e22f91cb182c48c47c02eb405fd689472aa7b7aa16dc",
"sha256:79f9b6f7c46ae1f8ded75f68cf8ad50e5729ed4d590c74840471fc2823457d04",
"sha256:7a33145e04d44ce95bcd71e522b478d282ad0eafaf34fe1ec5bbd73e662f22b6",
"sha256:857959354ae3a6fa3da6651b966d13b0a8bed6bbc87a0de7b38a549db1d2a359",
"sha256:87f37fe5130574ff76c17cab61e7d2538a16f843bb7bca8ebbc4b12de3078596",
"sha256:95d5251e4b5ca00061f9d9f3d6fe537247e145a8524ae9fd30a2f8fbce993b5b",
"sha256:9d1d3e63a4afdc29bd76ce6aa9d58c771cd1599fbba8cf5057e7860b203710dd",
"sha256:a36c5c154f9d42ec176e6e620cb0dd275744aa1d804786a71ac37dc3661a5e95",
"sha256:a6a5cb8809091ec9ac03edde9304b3ad82ad4466333432b16d78ef40e0cce0d5",
"sha256:ae5e35a2c189d397b91034642cb0eab0e346f776ec2eb44a49a459e6615d6e2e",
"sha256:b0f7d4a3df8f06cf49f9f121bead236e328074de6449866515cea4907bbc63d6",
"sha256:b75110fb114fa366b29a027d0c9be3709579602ae111ff61674d28c93606acca",
"sha256:ba5e697569f84b13640c9e193170e89c13c6244c24400fc57e88724ef610cd31",
"sha256:be2a9b390f77fd7676d80bc3cdc4f8edb940d8c198ed2d8c0be1319018c778e1",
"sha256:ca1bd81f40adc59011f58159e4aa6445fc585a32bb8ac9badf7a2c1aa23822f2",
"sha256:d5d8555d9bfc3f02385c1c37e9f998e2011f0db4f90e250e5bc0c0a85a813085",
"sha256:e55e22ac0a30023426564b1059b035973ec82186ddddbac867078435801c7801",
"sha256:e90f17980e6ab0f3c2f3730e56d1fe9bcba1891eeea58966e89d352492cc74f4",
"sha256:ecbb7b01409e9b782df5ded849c178a0aa7c906cf8c5a67368047daab282b184",
"sha256:ed01918d545a38998bfa5902c7c00e0fee90e957ce036a4000a88e3fe2264917",
"sha256:edabd457cd23a02965166026fd9bfd196f4324fe6032e866d0f3bd0301cd486f",
"sha256:fdf1c1dc5bafc32bc5d08b054f94d659422b05aba244d6be4ddc1c72d9aa70fb"
],
"version": "==1.11.5"
},
"click": {
"hashes": [
"sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
"sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
],
"index": "pypi",
"version": "==6.7"
},
"dnspython": {
"hashes": [
"sha256:40f563e1f7a7b80dc5a4e76ad75c23da53d62f1e15e6e517293b04e1f84ead7c",
"sha256:861e6e58faa730f9845aaaa9c6c832851fbf89382ac52915a51f89c71accdd31"
],
"version": "==1.15.0"
},
"eventlet": {
"hashes": [
"sha256:c584163e006e613707e224552fafc63e4e0aa31d7de0ab18b481ac0b385254c8",
"sha256:d9d31a3c8dbcedbcce5859a919956d934685b17323fc80e1077cb344a2ffa68d"
],
"index": "pypi",
"version": "==0.24.1"
},
"flask": {
"hashes": [
"sha256:2271c0070dbcb5275fad4a82e29f23ab92682dc45f9dfbc22c02ba9b9322ce48",
@ -31,13 +131,67 @@
"index": "pypi",
"version": "==1.0.2"
},
"gunicorn": {
"flask-limiter": {
"hashes": [
"sha256:7ef2b828b335ed58e3b64ffa84caceb0a7dd7c5ca12f217241350dec36a1d5dc",
"sha256:bc59005979efb6d2dd7d5ba72d99f8a8422862ad17ff3a16e900684630dd2a10"
"sha256:8cce98dcf25bf2ddbb824c2b503b4fc8e1a139154240fd2c60d9306bad8a0db8"
],
"index": "pypi",
"version": "==19.8.1"
"version": "==1.0.1"
},
"flask-login": {
"hashes": [
"sha256:c815c1ac7b3e35e2081685e389a665f2c74d7e077cb93cecabaea352da4752ec"
],
"index": "pypi",
"version": "==0.4.1"
},
"flask-migrate": {
"hashes": [
"sha256:83ebc105f87357ddd3968f83510d2b1092f006660b1c6ba07a4efce036ca567d",
"sha256:cd1b4e6cb829eeb41c02ad9202d83bef5f4b7a036dd9fad72ce96ad1e22efb07"
],
"index": "pypi",
"version": "==2.2.1"
},
"flask-sqlalchemy": {
"hashes": [
"sha256:3bc0fac969dd8c0ace01b32060f0c729565293302f0c4269beed154b46bec50b",
"sha256:5971b9852b5888655f11db634e87725a9031e170f37c0ce7851cf83497f56e53"
],
"index": "pypi",
"version": "==2.3.2"
},
"greenlet": {
"hashes": [
"sha256:0411b5bf0de5ec11060925fd811ad49073fa19f995bcf408839eb619b59bb9f7",
"sha256:131f4ed14f0fd28d2a9fa50f79a57d5ed1c8f742d3ccac3d773fee09ef6fe217",
"sha256:13510d32f8db72a0b3e1720dbf8cba5c4eecdf07abc4cb631982f51256c453d1",
"sha256:31dc4d77ef04ab0460d024786f51466dbbc274fda7c8aad0885a6df5ff8d642e",
"sha256:35021d9fecea53b21e4defec0ff3ad69a8e2b75aca1ceddd444a5ba71216547e",
"sha256:426a8ef9e3b97c27e841648241c2862442c13c91ec4a48c4a72b262ccf30add9",
"sha256:58217698193fb94f3e6ff57eed0ae20381a8d06c2bc10151f76c06bb449a3a19",
"sha256:5f45adbbb69281845981bb4e0a4efb8a405f10f3cd6c349cb4a5db3357c6bf93",
"sha256:5fdb524767288f7ad161d2182f7ed6cafc0a283363728dcd04b9485f6411547c",
"sha256:71fbee1f7ef3fb42efa3761a8faefc796e7e425f528de536cfb4c9de03bde885",
"sha256:80bd314157851d06f7db7ca527082dbb0ee97afefb529cdcd59f7a5950927ba0",
"sha256:b843c9ef6aed54a2649887f55959da0031595ccfaf7e7a0ba7aa681ffeaa0aa1",
"sha256:c6a05ef8125503d2d282ccf1448e3599b8a6bd805c3cdee79760fa3da0ea090e",
"sha256:deeda2769a52db840efe5bf7bdf7cefa0ae17b43a844a3259d39fb9465c8b008",
"sha256:e66f8b09eec1afdcab947d3a1d65b87b25fde39e9172ae1bec562488335633b4",
"sha256:e8db93045414980dbada8908d49dbbc0aa134277da3ff613b3e548cb275bdd37",
"sha256:f1cc268a15ade58d9a0c04569fe6613e19b8b0345b64453064e2c3c6d79051af",
"sha256:fe3001b6a4f3f3582a865b9e5081cc548b973ec20320f297f5e2d46860e9c703",
"sha256:fe85bf7adb26eb47ad53a1bae5d35a28df16b2b93b89042a3a28746617a4738d"
],
"version": "==0.4.14"
},
"gunicorn": {
"hashes": [
"sha256:aa8e0b40b4157b36a5df5e599f45c9c76d6af43845ba3b3b0efe2c70473c2471",
"sha256:fa2662097c66f920f53f70621c6c58ca4a3c4d3434205e608e121b5b3b71f4f3"
],
"index": "pypi",
"version": "==19.9.0"
},
"itsdangerous": {
"hashes": [
@ -52,12 +206,64 @@
],
"version": "==2.10"
},
"limits": {
"hashes": [
"sha256:9df578f4161017d79f5188609f1d65f6b639f8aad2914c3960c9252e56a0ff95",
"sha256:a017b8d9e9da6761f4574642149c337f8f540d4edfe573fb91ad2c4001a2bc76"
],
"version": "==1.3"
},
"mako": {
"hashes": [
"sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae"
],
"version": "==1.0.7"
},
"markupsafe": {
"hashes": [
"sha256:a6be69091dac236ea9c6bc7d012beab42010fa914c459791d627dad4910eb665"
],
"version": "==1.0"
},
"monotonic": {
"hashes": [
"sha256:23953d55076df038541e648a53676fb24980f7a1be290cdda21300b3bc21dfb0",
"sha256:552a91f381532e33cbd07c6a2655a21908088962bb8fa7239ecbcc6ad1140cc7"
],
"version": "==1.5"
},
"pycparser": {
"hashes": [
"sha256:99a8ca03e29851d96616ad0404b4aad7d9ee16f25c9f9708a11faf2810f7b226"
],
"version": "==2.18"
},
"python-dateutil": {
"hashes": [
"sha256:1adb80e7a782c12e52ef9a8182bebeb73f1d7e24e374397af06fb4956c8dc5c0",
"sha256:e27001de32f627c22380a688bcc43ce83504a7bc5da472209b4c70f02829f0b8"
],
"version": "==2.7.3"
},
"python-editor": {
"hashes": [
"sha256:a3c066acee22a1c94f63938341d4fb374e3fdd69366ed6603d7b24bed1efc565"
],
"version": "==1.0.3"
},
"six": {
"hashes": [
"sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
"sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
],
"version": "==1.11.0"
},
"sqlalchemy": {
"hashes": [
"sha256:ef6569ad403520ee13e180e1bfd6ed71a0254192a934ec1dbd3dbf48f4aa9524"
],
"version": "==1.2.11"
},
"werkzeug": {
"hashes": [
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",

38
README.md Normal file
View File

@ -0,0 +1,38 @@
# Share
because ftp was too easy
## Quickstart
It's easy peasy my dude
1. Install pip and pipenv
```bash
apt install pip3
pip3 install pipenv
apt install npm
npm install bower
```
2. Get dependencies
```bash
pipenv install
bower install
```
3. Initialize
```bash
pipenv run python setup.py serve_dir [--secret SECRET_KEY] [--db CONNECTION_STRING]
```
4. Run it
+ For testing
```bash
pipenv shell
FLASK_APP="notpiracyiswear.py" flask run
```
+ For production
```bash
pipenv run gunicorn notpiracyiswear:app -k 'eventlet'
```

21
bower.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "skehsucks-share",
"authors": [
"Derek Schmidt <skehmatics@gmail.com>"
],
"description": "ftp but worse",
"main": "notpiracyiswear.py",
"license": "GPL",
"homepage": "",
"private": true,
"dependencies": {
"webcomponentsjs": "^2.0.1",
"vaadin-upload": "^4.1.0",
"clipboard-copy": "advanced-rest-client/clipboard-copy#^2.0.1",
"paper-tooltip": "PolymerElements/paper-tooltip#^2.1.1",
"paper-toggle-button": "PolymerElements/paper-toggle-button#^2.1.1"
},
"resolutions": {
"webcomponentsjs": "^v1.1.0"
}
}

1
migrations/README Normal file
View File

@ -0,0 +1 @@
Generic single-database configuration.

45
migrations/alembic.ini Normal file
View File

@ -0,0 +1,45 @@
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S

88
migrations/env.py Normal file
View File

@ -0,0 +1,88 @@
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
import logging
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
logger = logging.getLogger('alembic.env')
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url',
current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url, compare_type=True)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
# this callback is used to prevent an auto-migration from being generated
# when there are no changes to the schema
# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html
def process_revision_directives(context, revision, directives):
if getattr(config.cmd_opts, 'autogenerate', False):
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
logger.info('No changes in schema detected.')
engine = engine_from_config(config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)
connection = engine.connect()
context.configure(connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives,
compare_type=True,
**current_app.extensions['migrate'].configure_args)
try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()

24
migrations/script.py.mako Normal file
View File

@ -0,0 +1,24 @@
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
"""
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}

View File

@ -0,0 +1,43 @@
"""empty message
Revision ID: 49984f04bc27
Revises: ea5307f715d1
Create Date: 2018-09-08 10:28:27.031642
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '49984f04bc27'
down_revision = 'ea5307f715d1'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('user', sa.Column('can_manage', sa.Boolean(), nullable=False, server_default=sa.false()))
op.add_column('user', sa.Column('can_refer', sa.Boolean(), nullable=False, server_default=sa.false()))
op.add_column('user', sa.Column('can_upload', sa.Boolean(), nullable=False, server_default=sa.false()))
user = sa.sql.table('user',
sa.sql.column('username', sa.String),
sa.sql.column('can_refer', sa.Boolean()),
sa.sql.column('can_upload', sa.Boolean()),
sa.sql.column('can_manage', sa.Boolean())
)
op.execute(
user.update().\
where(user.c.username==op.inline_literal('admin')).\
values({'can_refer':op.inline_literal(True), 'can_upload':op.inline_literal(True), 'can_manage':op.inline_literal(True)})
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('user', 'can_upload')
op.drop_column('user', 'can_refer')
op.drop_column('user', 'can_manage')
# ### end Alembic commands ###

View File

@ -0,0 +1,38 @@
"""empty message
Revision ID: 8c5a3947f711
Revises:
Create Date: 2018-05-31 09:50:58.314665
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8c5a3947f711'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=50), nullable=False),
sa.Column('password', sa.String(length=64), nullable=False),
sa.Column('active', sa.Boolean(), nullable=False),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_user_active'), 'user', ['active'], unique=False)
op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_user_username'), table_name='user')
op.drop_index(op.f('ix_user_active'), table_name='user')
op.drop_table('user')
# ### end Alembic commands ###

View File

@ -0,0 +1,38 @@
"""empty message
Revision ID: bb298ef84235
Revises: 8c5a3947f711
Create Date: 2018-05-31 09:53:34.164700
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'bb298ef84235'
down_revision = '8c5a3947f711'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('referal',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('key', sa.String(length=36), nullable=True),
sa.Column('user_id', sa.Integer(), nullable=False),
sa.Column('message', sa.String(), nullable=True),
sa.Column('kwargs', sa.PickleType(), nullable=True),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_referal_key'), 'referal', ['key'], unique=False)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_referal_key'), table_name='referal')
op.drop_table('referal')
# ### end Alembic commands ###

View File

@ -0,0 +1,34 @@
"""empty message
Revision ID: ea5307f715d1
Revises: bb298ef84235
Create Date: 2018-05-31 15:47:34.645982
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ea5307f715d1'
down_revision = 'bb298ef84235'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('referal', 'key',
existing_type=sa.String(length=36),
type_=sa.String(length=43),
existing_nullable=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.alter_column('referal', 'key',
existing_type=sa.String(length=43),
type_=sa.String(length=36),
existing_nullable=True)
# ### end Alembic commands ###

59
models.py Executable file
View File

@ -0,0 +1,59 @@
from flask_sqlalchemy import SQLAlchemy
from flask_login import UserMixin
import bcrypt
import secrets
db = SQLAlchemy()
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(50), unique=True, nullable=False, index=True)
password = db.Column(db.String(64), nullable=False)
active = db.Column(db.Boolean(), nullable=False, index=True)
can_refer = db.Column(db.Boolean(), nullable=False)
can_upload = db.Column(db.Boolean(), nullable=False)
can_manage = db.Column(db.Boolean(), nullable=False)
referals = db.relationship('Referal', backref='user', lazy=True)
def __init__(self, username, password, active=True, can_refer=False, can_upload=False, can_manage=False):
self.username = username
self.set_password(password)
self.active = active
self.can_refer = can_refer
self.can_upload = can_upload
self.can_manage = can_manage
def __repr__(self):
return "<User {username} : active = {is_active}>".format(username=self.username, is_active=self.active)
def set_password(self, password):
if len(password) > 72:
raise AttributeError("Password to hash is longer than 72 characters (bcrypt truncates after 72)")
self.password = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
def check_password(self, password):
return bcrypt.checkpw(password.encode(), self.password)
def is_active(self):
return self.active
def get_id(self):
return self.id
class Referal(db.Model):
id = db.Column(db.Integer, primary_key=True)
key = db.Column(db.String(43), index=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
message = db.Column(db.String(), nullable=True)
kwargs = db.Column(db.PickleType, nullable=True)
def __init__(self, user, message=None, **kwargs):
self.user_id = user.id
self.key = secrets.token_urlsafe()
self.message = message
self.kwargs = kwargs
def __repr__(self):
return "<Referal {id} : user = {user}>".format(id=self.id, user=self.user.username)

View File

@ -1,24 +1,197 @@
from flask import Flask, render_template, send_from_directory, redirect, url_for
from flask import Flask, render_template, flash, send_from_directory, redirect, request, session, url_for, abort
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager, current_user, login_user, logout_user, login_required, fresh_login_required
from flask_limiter import Limiter
import flask_limiter.util
from operator import itemgetter
from werkzeug.utils import secure_filename
import os
import json
from models import db, User, Referal
app = Flask(__name__)
app.config.from_pyfile('settings.cfg')
app.config.from_json('config.json')
db.app = app
db.init_app(app)
migrate = Migrate(app, db)
login_manager = LoginManager()
login_manager.init_app(app)
login_manager.login_view = "login"
login_manager.login_message = "slap them creds in to continue"
login_manager.needs_refresh_message = "no cookies for you - log in properly to continue"
login_manager.needs_refresh_message_category = 'info'
@login_manager.user_loader
def load_user(id):
return User.query.get(id)
limiter = Limiter(app, key_func=flask_limiter.util.get_ipaddr, headers_enabled=True)
def is_secure_path(path, servepath=None):
realpath = os.path.abspath(path) + os.path.sep
if servepath is None:
servepath = os.path.abspath(app.config['SERVE_DIR'])
else:
servepath = os.path.abspath(servepath)
return realpath.startswith(servepath)
@app.route('/')
def index():
# TODO: Login / user management here
return redirect(url_for('browse'))
if current_user.is_authenticated:
return redirect(url_for('browse'))
else:
return redirect(url_for('login'))
@app.route('/login', methods=['GET', 'POST'])
@limiter.limit("8/minute;1/second", exempt_when=lambda : request.method == 'GET')
def login():
if request.method == 'GET':
if current_user.is_authenticated:
return redirect(request.args.get('next') or url_for('index'))
else:
return render_template('login.html')
else:
username = request.form['username']
password = request.form['password']
user = User.query.filter_by(username=username).first()
if user is None:
flash("username / password all fucked up", 'error')
return redirect(request.script_root + request.path)
if user.check_password(password) == True:
success = login_user(user)
if success:
flash("holy cow you are logged in!", 'info')
return redirect(request.args.get('next') or url_for('index'))
else:
flash("oof ouch u banned my dude", 'error')
return redirect(request.script_root + request.path)
else:
flash("username / password all fucked up", 'error')
return redirect(request.script_root + request.path)
@app.route('/signup', methods=['GET', 'POST'])
@limiter.limit("5/minute;1/second", exempt_when=lambda : request.method == 'GET')
def signup():
referal_key = request.args.get('referalkey')
if not referal_key:
return render_template('genericerror.html', message="I don't trust y'all. Signups are by referal only rn.")
referal = Referal.query.filter_by(key=referal_key).first()
if not referal:
return render_template('genericerror.html', message="Someone gave you a bad referal code. What a dumbass. \nGo ask for a new one.")
if not referal.user.active == True:
return render_template('genericerror.html', message="Looks like {user} got a good 'ol banaroni. Referals from them aren't valid anymore. \nSorry pal.".format(user=referal.user.username))
if request.method == 'GET':
return render_template('signup.html', referer_username=referal.user.username, referal_message=referal.message or "oh worm? make an account my dude")
else:
username = request.form['username']
password = request.form['password']
referal_kwargs = referal.kwargs or {}
new_user = User(username, password, **referal_kwargs)
db.session.add(new_user)
db.session.delete(referal)
db.session.commit()
login_success = login_user(new_user)
if login_success:
return redirect(url_for('browse'))
else:
flash("couldn't log you in because ?? guess you'll have to do it yourself", 'error')
return redirect(url_for('login'))
@app.route('/logout')
@login_required
def logout():
logout_user()
flash("bye binch", 'info')
return redirect(url_for('login'))
@app.route('/upload/', methods=['GET', 'POST'])
@app.route('/upload/<path:path>', methods=['GET', 'POST'])
@login_required
def upload(path=None):
if not current_user.can_upload:
abort(403)
path = path if path is not None else ''
if not is_secure_path(os.path.join(app.config['SERVE_DIR'], path)):
abort(400)
if not os.path.exists(os.path.join(app.config['SERVE_DIR'], path)):
abort(404)
if request.method == 'GET':
return render_template('upload.html')
else:
if 'file' in request.files and secure_filename(request.files['file'].filename) != '':
file = request.files['file']
filename = secure_filename(file.filename)
fullpath = os.path.join(app.config['SERVE_DIR'], path, filename)
if os.path.exists(fullpath):
return json.dumps({'status': 'error', 'message': "Filename already exists"}), 409
file.save(fullpath)
relpath = os.path.join(path, filename)
return json.dumps({'status': 'success', 'link': url_for('files', path = relpath, _external = True)}), 201
else:
return json.dumps({'status': 'error', 'message': "No valid file attached to this request"}), 400
@app.route('/refer', methods=['GET', 'POST'])
@fresh_login_required
@limiter.limit("50/hour;2/second", key_func=lambda : current_user, exempt_when=lambda : request.method == 'GET')
def refer():
if not current_user.can_refer:
abort(403)
if request.method == 'GET':
return render_template('new_referal.html')
else:
can_refer = request.form.get('refer', 'off') == 'on'
can_upload = request.form.get('upload', 'off') == 'on'
can_manage = request.form.get('manage', 'off') == 'on'
if (not current_user.can_refer and can_refer) or (not current_user.can_upload and can_upload) or (not current_user.can_manage and can_manage):
flash('You cant just grant permissions you yourself dont have, dingo', 'error')
return redirect(url_for('refer'))
message = request.form.get('message', None)
referal = Referal(current_user, message=message, can_refer=can_refer, can_upload=can_upload, can_manage=can_manage)
db.session.add(referal)
db.session.commit()
return redirect(url_for('show_referal', key=referal.key))
@app.route('/refer/show', methods=['GET'])
@login_required
def show_referal():
if not current_user.can_refer:
abort(403)
return render_template('show_referal.html', key=request.args.get('key'))
@app.route('/browse/')
@app.route('/browse/<path:path>')
def browse(path = None):
# Search for files in path
searchpath = os.path.join('files', path) if path is not None else 'files'
@login_required
def browse(path=None):
searchpath = os.path.join(app.config['SERVE_DIR'], path) if path is not None else app.config['SERVE_DIR']
if not is_secure_path(searchpath):
abort(400)
# 404 if no such path exists
if not os.path.exists(searchpath):
return render_template('404.html'), 404
abort(404)
if not os.path.isdir(searchpath):
return redirect(url_for('files', path=path), code=301)
@ -37,9 +210,35 @@ def browse(path = None):
# Render out the item browser
return render_template('items.html', files = file_size_isdir_tuple_list, path = os.path.normpath(path) + '/' if path is not None else '')
# Expecting lots of traffic? Do these via nginx before you kill your server
@app.route('/files/<path:path>')
@login_required
def files(path):
return send_from_directory('files', path)
if not is_secure_path(os.path.join(app.config['SERVE_DIR'], path)):
abort(400)
return send_from_directory(app.config['SERVE_DIR'], path)
@app.route('/components/<path:path>')
def components(path):
if not is_secure_path(os.path.join('static', 'components', path), servepath=os.path.join('static', 'components')):
abort(400)
return send_from_directory(os.path.join('static', 'components'), path)
@app.route('/custom-components/<path:path>')
def custom_components(path):
if not is_secure_path(os.path.join('static', 'custom-components', path), servepath=os.path.join('static', 'custom-components')):
abort(400)
return send_from_directory(os.path.join('static', 'custom-components'), path)
# Boring stuff
@app.errorhandler(400)
def bad_request(e):
return render_template('genericerror.html', message="The request you sent didn't check out."), 400
@app.errorhandler(403)
def forbidden(e):
return render_template('403.html'), 403
@app.errorhandler(404)
def page_not_found(e):
@ -49,5 +248,9 @@ def page_not_found(e):
def internal_error(e):
return render_template('500.html'), 500
@app.errorhandler(429)
def rate_limit(e):
return render_template('429.html', back=request.script_root + request.path), 429
if __name__ == "__main__":
app.run()

View File

@ -1 +0,0 @@
DEBUG = False

30
setup.py Normal file
View File

@ -0,0 +1,30 @@
from models import User
from alembic import command
import json
import secrets
import click
@click.command()
@click.argument("serve_dir")
@click.option('--secret', help="Secret key to use. A random hex string will be generated if not provided")
@click.option('--db', 'connection', help="Database connection string. Defaults to \"sqlite:///test.db\"")
def main(serve_dir, secret=None, connection=None):
config = {"SERVE_DIR": serve_dir, "SECRET_KEY": secret or secrets.token_hex(), "SQLALCHEMY_DATABASE_URI": connection or 'sqlite:///test.db'}
with open('config.json', 'w') as f:
json.dump(config, f, indent=2)
# HACK: this is probably not the best way to make sure the built config is applied before we do anything else
from notpiracyiswear import app, db, migrate
db.create_all()
admin = User('admin', app.config['SECRET_KEY'], can_refer=True, can_upload=True)
db.session.add(admin)
db.session.commit()
config = migrate.get_config()
with app.app_context():
command.stamp(config, 'head', sql=False, tag=None)
if __name__ == '__main__':
main()

View File

@ -0,0 +1,352 @@
<!--
@license
Copyright (c) 2017 Vaadin Ltd.
This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
-->
<link rel="import" href="../components/polymer/polymer-element.html">
<link rel="import" href="../components/vaadin-themable-mixin/vaadin-themable-mixin.html">
<link rel="import" href="../components/vaadin-progress-bar/src/vaadin-progress-bar.html">
<link rel="import" href="../components/vaadin-upload/src/vaadin-upload-icons.html">
<link rel="import" href="../components/clipboard-copy/clipboard-copy.html">
<link rel="import" href="../components/paper-tooltip/paper-tooltip.html">
<!-- chrome is pretty serious about its scopes, so we have to get this twice. -->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<dom-module id="vaadin-permalinked-upload-file">
<template>
<style>
:host {
display: block;
}
[hidden] {
display: none;
}
</style>
<style include="lumo-field-button">
:host {
padding: var(--lumo-space-s) 0;
}
:host(:not(:first-child)) {
border-top: 1px solid var(--lumo-contrast-10pct);
}
[part="row"] {
display: flex;
align-items: baseline;
justify-content: space-between;
}
[part="status"],
[part="error"] {
color: var(--lumo-secondary-text-color);
font-size: var(--lumo-font-size-s);
}
[part="info"] {
display: flex;
align-items: baseline;
flex: auto;
}
[part="meta"] {
width: 0.001px;
flex: 1 1 auto;
}
[part="name"] {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
[part="commands"] {
display: flex;
align-items: baseline;
flex: none;
}
[part="done-icon"],
[part="warning-icon"] {
margin-right: var(--lumo-space-xs);
}
/* When both icons are hidden, let us keep space for one */
[part="done-icon"][hidden] + [part="warning-icon"][hidden] {
display: block !important;
visibility: hidden;
}
[part="done-icon"],
[part="warning-icon"] {
font-size: var(--lumo-icon-size-m);
font-family: 'lumo-icons';
line-height: 1;
}
[part="start-button"],
[part="retry-button"],
[part="clear-button"],
[part="copy-button"] {
flex: none;
margin-left: var(--lumo-space-xs);
}
[part="done-icon"]::before,
[part="warning-icon"]::before,
[part="start-button"]::before,
[part="retry-button"]::before,
[part="clear-button"]::before,
[part="copy-button"] i {
vertical-align: -.25em;
}
[part="done-icon"]::before {
content: var(--lumo-icons-checkmark);
color: var(--lumo-primary-text-color);
}
[part="warning-icon"]::before {
content: var(--lumo-icons-error);
color: var(--lumo-error-text-color);
}
[part="start-button"]::before {
content: var(--lumo-icons-play);
}
[part="retry-button"]::before {
content: var(--lumo-icons-reload);
}
[part="clear-button"]::before {
content: var(--lumo-icons-cross);
}
[part="error"] {
color: var(--lumo-error-text-color);
}
[part="progress"] {
width: auto;
margin-left: calc(var(--lumo-icon-size-m) + var(--lumo-space-xs));
margin-right: calc(var(--lumo-icon-size-m) + var(--lumo-space-xs));
}
[part="progress"][complete],
[part="progress"][error] {
display: none;
}
</style>
<div part="row">
<div part="info">
<div part="done-icon" hidden$="[[!file.complete]]"></div>
<div part="warning-icon" hidden$="[[!file.error]]"></div>
<slot name="custom-icon"></slot>
<div part="meta">
<div part="name" id="name">[[file.name]]</div>
<div part="status" hidden$="[[!file.url]]" id="status">Avalible at <a href="[[file.url]]">[[file.url]]</a></div>
<div part="error" id="error" hidden$="[[!file.error]]">[[_errorOrMessage(file.error, file.message)]]</div>
</div>
</div>
<div part="commands">
<div part="start-button" file-event="file-start" on-click="_fireFileEvent" hidden$="[[!file.held]]"></div>
<div part="retry-button" file-event="file-retry" on-click="_fireFileEvent" hidden$="[[!file.error]]"></div>
<div part="clear-button" file-event="file-abort" on-click="_fireFileEvent" hidden$="[[file.complete]]"></div>
<div part="copy-button" on-click="_copyUrl" hidden$="[[!file.complete]]">
<clipboard-copy id="clipboardomatic" content="[[file.url]]"></clipboard-copy>
<i class="material-icons">file_copy</i>
</div>
</div>
<paper-tooltip id="copied_notif" for="copy-button" position="right">Copied to clipboard</paper-tooltip>
</div>
<vaadin-progress-bar
part="progress"
id="progress"
value$="[[_formatProgressValue(file.progress)]]"
error$="[[file.error]]"
indeterminate$="[[file.indeterminate]]"
uploading$="[[file.uploading]]"
complete$="[[file.complete]]"
hidden$="[[file.complete]]">
</vaadin-progress-bar>
</template>
<script>
(function() {
/**
* `<vaadin-permalinked-upload-file>` element represents a file in the file list of `<vaadin-upload>`.
*
* ### Styling
*
* The following shadow DOM parts are available for styling:
*
* Part name | Description
* ---|---
* `row` | File container
* `info` | Container for file status icon, file name, status and error messages
* `done-icon` | File done status icon
* `warning-icon` | File warning status icon
* `meta` | Container for file name, status and error messages
* `name` | File name
* `error` | Error message, shown when error happens
* `status` | Status message
* `commands` | Container for file command icons
* `start-button` | Start file upload button
* `retry-button` | Retry file upload button
* `clear-button` | Clear file button
* `progress`| Progress bar
*
* The following state attributes are available for styling:
*
* Attribute | Description | Part name
* ---|---|---
* `error` | An error has happened during uploading | `:host`
* `indeterminate` | Uploading is in progress, but the progress value is unknown | `:host`
* `uploading` | Uploading is in progress | `:host`
* `complete` | Uploading has finished successfully | `:host`
*
* See [ThemableMixin how to apply styles for shadow parts](https://github.com/vaadin/vaadin-themable-mixin/wiki)
*
* @memberof Vaadin
* @mixes Vaadin.ThemableMixin
* @demo demo/index.html
*/
class PermalinkedUploadFileElement extends Vaadin.ThemableMixin(Polymer.Element) {
static get is() {
return 'vaadin-permalinked-upload-file';
}
static get properties() {
return {
file: Object
};
}
static get observers() {
return [
'_fileAborted(file.abort)',
'_toggleHostAttribute(file.error, "error")',
'_toggleHostAttribute(file.indeterminate, "indeterminate")',
'_toggleHostAttribute(file.uploading, "uploading")',
'_toggleHostAttribute(file.complete, "complete")',
];
}
_copyUrl(event) {
var success = this.$.clipboardomatic.copy();
if (success) {
var notif = this.$.copied_notif;
notif.show();
window.setTimeout(function() {
notif.hide();
}, 3000);
}
}
_errorOrMessage(error, message) {
if (message) {
return message;
} else {
return error;
}
}
_fileAborted(abort) {
if (abort) {
this._remove();
}
}
_remove() {
this.dispatchEvent(
new CustomEvent('file-remove', {
detail: {file: this.file},
bubbles: true,
composed: true
})
);
}
_formatProgressValue(progress) {
return progress / 100;
}
_fireFileEvent(e) {
e.preventDefault();
return this.dispatchEvent(
new CustomEvent(e.target.getAttribute('file-event'), {
detail: {file: this.file},
bubbles: true,
composed: true
})
);
}
_toggleHostAttribute(value, attributeName) {
const shouldHave = Boolean(value);
const has = this.hasAttribute(attributeName);
if (has !== shouldHave) {
if (shouldHave) {
this.setAttribute(attributeName, '');
} else {
this.removeAttribute(attributeName);
}
}
}
/**
* Fired when the retry button is pressed. It is listened by `vaadin-upload`
* which will start a new upload process of this file.
*
* @event file-retry
* @param {Object} detail
* @param {Object} detail.file file to retry upload of
*/
/**
* Fired when the start button is pressed. It is listened by `vaadin-upload`
* which will start a new upload process of this file.
*
* @event file-start
* @param {Object} detail
* @param {Object} detail.file file to start upload of
*/
/**
* Fired when abort button is pressed. It is listened by `vaadin-upload` which
* will abort the upload in progress, but will not remove the file from the list
* to allow the animation to hide the element to be run.
*
* @event file-abort
* @param {Object} detail
* @param {Object} detail.file file to abort upload of
*/
/**
* Fired after the animation to hide the element has finished. It is listened
* by `vaadin-upload` which will actually remove the file from the upload
* file list.
*
* @event file-remove
* @param {Object} detail
* @param {Object} detail.file file to remove from the upload of
*/
}
customElements.define(PermalinkedUploadFileElement.is, PermalinkedUploadFileElement);
/**
* @namespace Vaadin
*/
window.Vaadin = window.Vaadin || {};
Vaadin.PermalinkedUploadFileElement = PermalinkedUploadFileElement;
})();
</script>
</dom-module>

View File

@ -1,4 +0,0 @@
<svg fill="#000000" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
<path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/>
<path d="M0 0h24v24H0z" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 209 B

View File

@ -1,4 +0,0 @@
<svg fill="#000000" height="48" viewBox="0 0 24 24" width="48" xmlns="http://www.w3.org/2000/svg">
<path d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"/>
<path d="M0-.25h24v24H0z" fill="none"/>
</svg>

Before

Width:  |  Height:  |  Size: 214 B

View File

@ -4,40 +4,44 @@ body {
--trinary-background-color: #B0BEC5;
--foreground-color: #000000;
--secondary-foreground-color: #424242;
--foreground-disabled-color: #9e9e9e;
--info-color: #90caf9;
--error-color: #f06292;
--error-color-light: #f48fb1;
background-color: var(--background-color);
color: var(--foreground-color);
margin: 0;
height: 100vh;
height: 100%;
min-height: 100vh;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
p, b, i {
font-family: 'Roboto', sans-serif;
}
#mainwrapper {
height: 100%;
width: 100%;
flex: auto;
flex-grow: 3;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
align-items: flex-start;
}
#contentarea {
position: sticky;
top: 0px;
max-width: 600px;
min-width: 320px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex: auto;
padding: 8px 16px;
margin: auto;
box-sizing: border-box;
}
#contentarea > *:not(:last-child) {
margin-top: 0px;
@ -51,6 +55,116 @@ p, b, i {
color: var(--secondary-foreground-color);
}
#login_form {
padding-top: 0px;
}
input:not([type=file]) {
height: 40px;
background-color: var(--secondary-background-color);
border-style: none;
color: inherit;
padding: 4px 16px;
}
input[type=file] {
height: 40px;
/* background-color: var(--secondary-background-color); */
border-style: dashed;
border-color: var(--trinary-background-color);
border-width: 2px;
padding: 4px 16px;
}
button {
transition: background cubic-bezier(0.4, 0, 0.2, 1) 0.28s;
background-color: var(--secondary-background-color);
color: inherit;
border-style: none;
}
button:active {
transition: background cubic-bezier(0.4, 0, 0.2, 1) 0.05s;
background-color: var(--info-color);
}
#login_form > button {
height: 38px;
margin-top: 32px;
}
#login_form > *:not(:last-child) {
margin-bottom: 8px;
}
form {
display: flex;
flex-direction: column;
padding-top: 32px;
}
form > div {
display: flex;
flex-direction: row;
align-items: center;
}
#custom_message_text {
margin: 12px 0px;
}
form > button {
margin-top: 32px;
padding: 12px 24px;
width: 100%;
}
input[disabled] + .checkboxtext {
color: var(--foreground-disabled-color);
}
#link_copy {
margin-top: 12px;
color: var(--secondary-foreground-color);
}
@keyframes fade { from { opacity: 1; } to { opacity: 0; } }
@keyframes slide { from { bottom: 0px; } to { bottom: -100%; } }
#flash_wrapper {
position: fixed;
bottom: 0px;
width: 100%;
animation: slide cubic-bezier(0.4, 0, 0.2, 1) 1, fade 1;
animation-fill-mode: forwards;
animation-delay: 4s, 4.5s;
animation-duration: 1s, 0.5s;
pointer-events: none;
}
.flash {
width: 100%;
padding: 16px 32px;
}
.flash > p {
margin: 0px;
}
.flash {
background-color: var(--info-color);
}
@keyframes pulse {
0% { background-color: var(--error-color); }
20% { background-color: var(--error-color-light); }
100% { background-color: var(--error-color); }
}
.flash.error {
background-color: var(--error-color);
animation: pulse ease 2;
animation-fill-mode: backwards;
animation-duration: 0.5s;
}
.itemwrapper {
transition: background 0.28s cubic-bezier(0.4, 0, 0.2, 1);
background-color: var(--secondary-background-color);
@ -65,14 +179,53 @@ p, b, i {
display: flex;
flex-direction: row;
padding: 4px 16px;
align-items: center;
}
.item-name {
flex: auto;
min-width: 1%;
word-wrap: break-word;
padding-right: 8px;
}
.item-size {
white-space: nowrap;
}
.item-icon {
font-size: 42px;
}
#headerbar {
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
position: fixed;
top: 0px;
right: 0px;
margin: 16px;
padding: 0px;
z-index: 10;
}
#headerbar > .iconbutton {
margin-bottom: 16px;
}
#headerbar-text {
font-size: 16px;
text-align: center;
margin: 0px;
margin-right: auto;
display: none;
}
.iconbutton {
padding: 4px;
}
.iconbutton > img {
width: 32px;
}
.iconbutton > i {
font-size: 32px;
}
a.nostyle:link {
text-decoration: inherit;
@ -82,27 +235,59 @@ a.nostyle:visited {
text-decoration: inherit;
color: inherit;
}
#waifuwrapper {
height: 100%;
display: flex;
button.nostyle {
border: none;
background: transparent;
}
@media only screen and (max-width: 780px) {
#waifu {
display: none;
}
#waifu-lazyflexhack {
display: none;
}
}
#waifu-lazyflexhack {
#waifu-spacesaver {
width: 100%;
max-width: 30vw;
max-height: 100vh;
visibility: hidden;
}
#waifu {
/* visibility: hidden; */
position: fixed;
bottom: 0px;
right: 0px;
z-index: -1;
max-width: 30vw;
max-height: 100vh;
}
@media only screen and (max-width: 780px) {
body {
flex-direction: column;
}
#headerbar {
background-color: var(--info-color);
box-shadow: 0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.24);
flex-direction: row-reverse;
position: sticky;
width: 100%;
height: 48px;
margin: 0px;
padding: 4px 16px;
}
#headerbar > .iconbutton {
margin-bottom: 0px;
margin-left: 16px;
}
#headerbar-text {
display: inline-block;
}
.iconbutton > img {
width: 24px;
}
.iconbutton > i {
font-size: 24px;
}
#waifu {
display: none;
}
#waifu-spacesaver {
display: none;
}
}

7
templates/403.html Normal file
View File

@ -0,0 +1,7 @@
{% extends 'error.html' %}
{% block head %}
Get out
{% endblock %}
{% block disc %}
You're not welcome here &gt;:c
{% endblock %}

7
templates/429.html Normal file
View File

@ -0,0 +1,7 @@
{% extends 'error.html' %}
{% block head %}
sTOP IT
{% endblock %}
{% block disc %}
Whoa buddy calm down, you're going too fast. Go drink some tea and then <a href="{{ back }}">try again.</a>
{% endblock %}

View File

@ -1,39 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">
<title>it's not piracy i swear</title>
<meta name="description" content="ftp but worse">
<link href="/static/style.css" type="text/css" rel="stylesheet" />
</head>
<body>
<div id="mainwrapper">
<div id="contentarea">
{% for file, size in files %}
<a class="nostyle itemwrapper" href="/files/{{ file }}" download>
<div class="item">
<p class="item-name">{{ file }}</p>
<p class="item-size">{{ size }}</p>
</div>
</a>
{% else %}
<p id="nofileshead">
It's fucking nothing!
</p>
<p id="nofilesdisc">
No files avalible yet. Come back later, eh?
</p>
{% endfor %}
</div>
</div>
<div id="waifuwrapper">
<img id="waifu-lazyflexhack" src="/static/obligitorywaifu.png"></img>
<!-- <div id="waifu-spacesaver"></div> -->
</div>
<img id="waifu" src="/static/obligitorywaifu.png"></img>
</body>
</html>

View File

@ -9,7 +9,7 @@
<div class="item">
<p class="item-name">{{ file }}</p>
{% if isdir %}
<img src="{{ url_for('static', filename='ic_chevron_right_black.svg') }}" />
<i class="item-icon material-icons">chevron_right</i>
{% else %}
<p class="item-size">{{ size|filesizeformat }}</p>
{% endif %}
@ -29,7 +29,7 @@
<p class="item-name">
Back
</p>
<img src="{{ url_for('static', filename='ic_chevron_left_black.svg') }}" />
<i class="item-icon material-icons">chevron_left</i>
</div>
</a>
{% endif %}

12
templates/login.html Executable file
View File

@ -0,0 +1,12 @@
{% extends 'skel.html' %}
{% block body %}
<div id="mainwrapper">
<div id="contentarea">
<form id="login_form" action="" method="post">
<input id="username_input" name="username" type="text" placeholder="Username" required></input>
<input id="password_input" name="password" type="password" placeholder="Password" required></input>
<button type="submit">Sign in</button>
</form>
</div>
</div>
{% endblock %}

View File

@ -6,8 +6,6 @@
{% endblock %}
</div>
</div>
<div id="waifuwrapper">
<img id="waifu-lazyflexhack" src="{{ url_for('static', filename='obligitorywaifu.png') }}"></img>
</div>
<div id="waifu-spacesaver"></div>
<img id="waifu" src="{{ url_for('static', filename='obligitorywaifu.png') }}"></img>
{% endblock %}

View File

@ -0,0 +1,30 @@
{% extends 'skel.html' %}
{% block body %}
<div id="mainwrapper">
<div id="contentarea">
<p id="infohead">
Sharing is <span style='text-decoration: line-through;'>communism</span> caring
</p>
<p id="infodisc">
Referal links can be used only once to register a new user on this site, giving access to all it's content.
Give out with caution.
</p>
<form id="referal_form" action="" method="post">
<div id="refer_container">
<input id="refer_permission_checkbox" name="refer" type="checkbox"></input>
<p class="checkboxtext">Allow refering other users</p>
</div>
<div id="upload_container">
<input id="upload_permission_checkbox" name="upload" type="checkbox" {{ "disabled" if not current_user.can_upload }}></input>
<p class="checkboxtext">Allow uploading new files</p>
</div>
<div id="admin_container">
<input id="admin_permission_checkbox" name="manage" type="checkbox" {{ "disabled" if not current_user.can_manage }}></input>
<p class="checkboxtext">Allow administrative actions</p>
</div>
<input id="custom_message_text" name="message" placeholder="Custom message shown on signup page"></input>
<button type="submit">Generate referal link</button>
</form>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,24 @@
{% extends 'skel.html' %}
{% block body %}
<div id="mainwrapper">
<div id="contentarea">
<p id="infohead">
Sharing is <span style='text-decoration: line-through;'>communism</span> caring
</p>
<p id="infodisc">
Here's your shiny new referal link:
</p>
<input id="link_copy" readonly value="{{ url_for('signup', referalkey=key, _external=True) }}"></input>
</div>
</div>
<script>
document.querySelector('#link_copy').onclick = function(event) {
var sel, range;
sel = window.getSelection();
if(sel.toString() == '') {
event.target.select();
}
};
</script>
{% endblock %}

33
templates/signup.html Normal file
View File

@ -0,0 +1,33 @@
{% extends 'skel.html' %}
{% block body %}
<div id="mainwrapper">
<div id="contentarea">
<p id="infohead">
{{ referal_message }}
</p>
<p id="infodisc">
- {{ referer_username }}
</p>
<form id="login_form" action="" method="post" style="padding-top: 32px;">
<input id="username_input" name="username" type="text" placeholder="Username" required></input>
<input id="password_input" name="password" type="password" placeholder="Password" required></input>
<input id="password_confirm_input" name="password" type="password" placeholder="Confirm password" required></input>
<button type="submit">Sign up</button>
</form>
</div>
</div>
<script>
var password = document.getElementById("password_input"), confirm_password = document.getElementById("password_confirm_input");
function validatePassword(){
if(password.value != confirm_password.value) {
confirm_password.setCustomValidity("Passwords don't match");
} else {
confirm_password.setCustomValidity('');
}
}
password.onchange = validatePassword;
confirm_password.onkeyup = validatePassword;
</script>
{% endblock %}

View File

@ -7,13 +7,46 @@
<title>it's not piracy i swear</title>
<meta name="description" content="ftp but worse">
<link href="{{ url_for('static', filename='style.css') }}" type="text/css" rel="stylesheet" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
<link href="{{ url_for('static', filename='style.css') }}" type="text/css" rel="stylesheet" />
{% block imports %}
{% endblock %}
</head>
<body>
{% if current_user.is_authenticated %}
<div id="headerbar">
<a class="nostyle iconbutton" href="{{ url_for('logout') }}" title="Logout">
<i class="material-icons">exit_to_app</i>
</a>
{% if current_user.can_refer %}
<a class="nostyle iconbutton" href="{{ url_for('refer') }}" title="Add new user">
<i class="material-icons">person_add</i>
</a>
{% endif %}
{% if current_user.can_upload and path is defined %}
<a class="nostyle iconbutton" href="{{ url_for('upload', path=path) }}" title="Upload to current directory">
<i class="material-icons">cloud_upload</i>
</a>
{% endif %}
<p id="headerbar-text">it's not piracy, it's digiorno</p>
</div>
{% endif %}
{% block body %}
{% endblock %}
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div id="flash_wrapper">
{% for category, message in messages %}
<div class="flash {{ category }}">
<p>{{ message }}</p>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
</body>
</html>

81
templates/upload.html Normal file
View File

@ -0,0 +1,81 @@
{% extends 'skel.html' %}
{% block imports %}
<script src="{{ url_for('components', path='webcomponentsjs/webcomponents-loader.js') }}"></script>
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-bind.html') }}">
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-repeat.html') }}">
<link rel="import" href="{{ url_for('components', path='polymer/lib/elements/dom-if.html') }}">
<link rel="import" href="{{ url_for('components', path='vaadin-upload/vaadin-upload.html') }}">
<link rel="import" href="{{ url_for('custom_components', path='vaadin-permalinked-upload-file.html') }}">
<link rel="import" href="{{ url_for('components', path='paper-toggle-button/paper-toggle-button.html') }}">
{% endblock %}
{% block body %}
<div id="mainwrapper">
<div id="contentarea">
<p id="infohead">
Files for the file gods
</p>
<p id="infodisc">
Get your upload on my dude.
</p>
<form class="noscript" action="" method="POST" enctype="multipart/form-data">
<input id="upload_input" name="files" type="file" accept="image/*"></input>
<button id="upload_button" type="submit">Upload</button>
</form>
<dom-bind>
<template>
<vaadin-upload id="file_upload" files="{%raw%}{{files}}{%endraw%}" target="" method="POST" form-data-name="file" nodrop="[[nodrop]]">
<style is="custom-style">
[nodrop] [part="upload-button"] {
width: 100%;
}
[nodrop] #addFiles {
width: 100%;
}
</style>
<div slot="drop-label-icon"></div>
<span slot="drop-label" class="font-headline">or drag a file here</span>
<div slot="file-list">
<template is="dom-repeat" items="[[files]]" as="file">
<vaadin-permalinked-upload-file file="[[file]]"></vaadin-permalinked-upload-file>
</template>
</div>
</vaadin-upload>
</template>
</dom-bind>
</div>
</div>
<script>
function isES6()
{
try { Function("() => {};"); return true; }
catch(exception) { return false; }
}
window.addEventListener('WebComponentsReady', function() {
// Remove noscript / dinosaur componenets if both webcomponents and es6 avalible
if (isES6()){
var noscripts = document.querySelectorAll('.noscript');
for (var i = noscripts.length-1; i >= 0; i--){
noscripts[i].parentNode.removeChild(noscripts[i]);
}
} else {
// TODO: Abort polymer loading to fix IE become ing responsive for a bit
}
var upload = document.querySelector('vaadin-upload#file_upload');
var binder = document.querySelector('dom-bind');
upload.addEventListener('upload-response', function(event) {
response = JSON.parse(event.detail.xhr.response);
console.log(response);
if (response.status == 'success'){
event.detail.file.url = response.link;
} else {
event.detail.file.message = response.message;
}
});
binder.files = [];
});
</script>
{% endblock %}