Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
Chiefs
ChiefSend
Commits
91782440
Commit
91782440
authored
Dec 26, 2020
by
Lukas Böhm
🎱
Browse files
auth, public and remove shared view
parent
970ad0ef
Changes
11
Hide whitespace changes
Inline
Side-by-side
Dockerfile
View file @
91782440
...
...
@@ -14,6 +14,4 @@ COPY chiefsend.py chiefsend.py
ENV
FLASK_APP chiefsend.py
EXPOSE
5000
EXPOSE
6379
ENTRYPOINT
gunicorn -b :5000 --workers=3 --timeout=90 --graceful-timeout=30 --log-level=DEBUG --access-logfile - --error-logfile - chiefsend:app
app/__init__.py
View file @
91782440
...
...
@@ -8,6 +8,7 @@ basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
datadir
=
os
.
path
.
abspath
(
os
.
path
.
join
(
basedir
,
'data'
))
app
=
Flask
(
__name__
)
app
.
config
[
'SECRET_KEY'
]
=
os
.
getenv
(
'SECRET_KEY'
)
or
'jonas ist ein kek'
app
.
config
[
'MEDIA_LOCATION'
]
=
os
.
getenv
(
'MEDIA_LOCATION'
)
or
os
.
path
.
join
(
datadir
,
'media'
)
app
.
config
[
'SQLALCHEMY_DATABASE_URI'
]
=
os
.
getenv
(
'DATABASE_URI'
)
or
'sqlite:///'
+
os
.
path
.
join
(
datadir
,
'home.db'
)
...
...
@@ -17,9 +18,8 @@ app.config['REDIS_URI'] = os.getenv('REDIS_URI') or 'redis://'
db
=
SQLAlchemy
(
app
)
# task_queue = Queue(connection=Redis.from_url(app.config['REDIS_URI']))
task_queue
=
Queue
(
connection
=
Redis
(
host
=
'chiefsend-redis'
))
redis
=
Redis
.
from_url
(
app
.
config
[
'REDIS_URI'
])
task_queue
=
Queue
(
connection
=
redis
)
from
app.models
import
*
...
...
app/forms.py
View file @
91782440
...
...
@@ -11,8 +11,14 @@ class UploadForm(FlaskForm):
(
10080
,
'7 Tage'
),
(
43200
,
'1 Monat'
),
(
-
1
,
'Für immer'
)],
label
=
'Verfügbarkeit'
,
validators
=
[
DataRequired
()],
default
=
1440
,
coerce
=
float
)
download_limit
=
SelectField
(
choices
=
[(
1
,
'1 Download'
),
(
2
,
'2 Downloads'
),
(
5
,
'5 Downloads'
),
(
15
,
'15 Downloads'
),
(
50
,
'50 Downloads'
),
(
200
,
'200 Downloads'
),
(
-
1
,
'Unbegrenzt'
)],
choices
=
[(
1
,
'1 Download'
),
(
2
,
'2 Downloads'
),
(
5
,
'5 Downloads'
),
(
15
,
'15 Downloads'
),
(
50
,
'50 Downloads'
),
(
200
,
'200 Downloads'
),
(
-
1
,
'Unbegrenzt'
)],
label
=
'Downloads'
,
validators
=
[
DataRequired
()],
default
=
5
,
coerce
=
int
)
is_public
=
BooleanField
(
'Öffentlich'
,
default
=
False
)
password
=
Password
Field
(
'Passwort'
)
password
=
String
Field
(
'Passwort'
)
submit
=
SubmitField
(
'Hochladen'
)
class
AuthForm
(
FlaskForm
):
password
=
PasswordField
(
'Passwort'
,
validators
=
[
DataRequired
()])
submit
=
SubmitField
(
'Zugriff!'
)
app/routes.py
View file @
91782440
import
os
from
datetime
import
timezone
,
datetime
from
werkzeug.exceptions
import
HTTPException
from
werkzeug.security
import
generate_password_hash
,
check_password_hash
from
app
import
app
,
db
from
flask
import
redirect
,
url_for
,
render_template
,
send_from_directory
,
current_app
,
abort
,
request
,
jsonify
from
flask
import
redirect
,
url_for
,
render_template
,
send_from_directory
,
current_app
,
session
,
flash
from
app.models
import
Share
,
Attachment
from
app.forms
import
UploadForm
from
app.forms
import
UploadForm
,
AuthForm
from
app
import
task_queue
as
que
from
shutil
import
make_archive
,
rmtree
@
app
.
route
(
'/config'
)
def
see_config
():
return
jsonify
(
dict
(
redis
=
app
.
config
[
'REDIS_URI'
]))
def
get_and_check_share
(
share_id
):
def
get_and_check_share
(
share_id
)
->
Share
:
share
=
Share
.
query
.
get
(
share_id
)
if
share
is
None
:
return
None
if
share
.
download_limit
<=
0
:
if
share
.
download_limit
<=
0
or
datetime
.
now
()
>
share
.
expires
:
que
.
enqueue
(
delete_share
,
share
.
id
)
return
None
return
share
def
delete_share
(
share_id
):
print
(
f
'delete share:
{
share_id
}
'
)
print
(
f
'delete share:
{
share_id
}
'
)
with
app
.
app_context
():
share
=
Share
.
query
.
get_or_404
(
str
(
share_id
))
# delete files
...
...
@@ -78,7 +75,9 @@ def upload():
# create share
share
=
Share
(
name
=
str
(
form
.
name
.
data
),
timer
=
form
.
timer
.
data
,
download_limit
=
form
.
download_limit
.
data
download_limit
=
form
.
download_limit
.
data
,
is_public
=
form
.
is_public
.
data
,
password
=
form
.
password
.
data
)
db
.
session
.
add
(
share
)
db
.
session
.
flush
()
...
...
@@ -92,34 +91,34 @@ def upload():
os
.
path
.
join
(
current_app
.
config
[
'MEDIA_LOCATION'
],
share
.
id
)
)
# put timer into scheduler queue
print
(
share
.
expires
)
if
share
.
expires
:
que
.
enqueue_at
(
datetime
=
share
.
expires
,
f
=
delete_share
,
args
=
[
share
.
id
])
# wrap it up
db
.
session
.
commit
()
return
url_for
(
'
share
d'
,
share_id
=
share
.
id
)
return
render_template
(
'Shared.html'
,
share
=
share
,
url
=
url_for
(
'
downloa
d'
,
share_id
=
share
.
id
)
)
return
render_template
(
'Upload.html'
,
form
=
form
)
@
app
.
route
(
'/shared/<string:share_id>'
)
def
shared
(
share_id
):
share
=
get_and_check_share
(
share_id
)
if
share
is
None
:
return
redirect
(
url_for
(
'expired'
))
return
render_template
(
'Shared.html'
,
share
=
share
,
url
=
url_for
(
'download'
,
share_id
=
share
.
id
))
@
app
.
route
(
'/d/<string:share_id>'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/d/<string:share_id>/'
,
methods
=
[
'GET'
])
def
download
(
share_id
):
share
=
get_and_check_share
(
share_id
)
if
share
is
None
:
return
redirect
(
url_for
(
'expired'
))
if
share
.
password
:
if
share_id
not
in
session
:
return
redirect
(
url_for
(
'share_auth'
,
share_id
=
share_id
))
return
render_template
(
'Download.html'
,
up
=
share
)
@
app
.
route
(
'/
media
/<string:share_id>/<string:filename>'
,
methods
=
[
'GET'
])
@
app
.
route
(
'/
d
/<string:share_id>/<string:filename>'
,
methods
=
[
'GET'
])
def
media
(
share_id
,
filename
):
share
=
get_and_check_share
(
share_id
)
if
share
is
None
:
return
redirect
(
url_for
(
'expired'
))
if
share
.
password
:
if
share_id
not
in
session
:
return
redirect
(
url_for
(
'share_auth'
,
share_id
=
share_id
))
share
.
download_limit
-=
1
db
.
session
.
commit
()
return
send_from_directory
(
os
.
path
.
join
(
current_app
.
config
[
'MEDIA_LOCATION'
],
share
.
id
),
...
...
@@ -130,5 +129,28 @@ def media(share_id, filename):
def
zip
(
share_id
):
share
=
get_and_check_share
(
share_id
)
if
share
is
None
:
return
redirect
(
url_for
(
'expired'
))
if
share
.
password
:
if
share_id
not
in
session
:
return
redirect
(
url_for
(
'share_auth'
,
share_id
=
share_id
))
return
send_from_directory
(
current_app
.
config
[
'MEDIA_LOCATION'
],
filename
=
str
(
share
.
id
)
+
'.zip'
,
as_attachment
=
True
)
@
app
.
route
(
'/d/<string:share_id>/auth'
,
methods
=
[
'GET'
,
'POST'
])
def
share_auth
(
share_id
):
share
=
get_and_check_share
(
share_id
)
if
share
is
None
:
return
redirect
(
url_for
(
'expired'
))
form
=
AuthForm
()
if
form
.
validate_on_submit
():
if
check_password_hash
(
share
.
password
,
form
.
password
.
data
):
session
[
share_id
]
=
datetime
.
now
(
timezone
.
utc
)
return
redirect
(
url_for
(
'download'
,
share_id
=
share_id
))
else
:
for
field
,
error
in
form
.
errors
.
items
():
from
markupsafe
import
Markup
flash
(
Markup
(
f
'<b>
{
field
}
:</b>
{
error
}
'
),
category
=
'danger'
)
flash
(
'Zugriff verweigert.'
,
category
=
'danger'
)
return
render_template
(
'DownloadAuth.html'
,
form
=
form
)
app/static/css/bootstrap-switches.css
0 → 100644
View file @
91782440
/*
* ==========================================
* CUSTOM UTIL CLASSES
* ==========================================
*/
/* toggle switches with bootstrap default colors */
.custom-control-input-success
:checked
~
.custom-control-label
::before
{
background-color
:
#28a745
!important
;
border-color
:
#28a745
!important
;
}
.custom-control-input-danger
:checked
~
.custom-control-label
::before
{
background-color
:
#dc3545
!important
;
border-color
:
#dc3545
!important
;
}
.custom-control-input-warning
:checked
~
.custom-control-label
::before
{
background-color
:
#ffc107
!important
;
border-color
:
#ffc107
!important
;
}
.custom-control-input-info
:checked
~
.custom-control-label
::before
{
background-color
:
#17a2b8
!important
;
border-color
:
#17a2b8
!important
;
}
/* Large toggl switches */
.custom-switch-lg
.custom-control-label
::before
{
left
:
-2.25rem
;
width
:
3rem
;
border-radius
:
1.5rem
;
}
.custom-switch-lg
.custom-control-label
::after
{
top
:
calc
(
.25rem
+
3px
);
left
:
calc
(
-2.25rem
+
4px
);
width
:
calc
(
1.5rem
-
6px
);
height
:
calc
(
1.5rem
-
6px
);
border-radius
:
1.5rem
;
}
.custom-switch-lg
.custom-control-input
:checked
~
.custom-control-label
::after
{
transform
:
translateX
(
1.4rem
);
}
.custom-switch-lg
.custom-control-label
::before
{
height
:
1.5rem
;
}
.custom-switch-lg
.custom-control-label
{
padding-left
:
1.5rem
;
line-height
:
1.7rem
;
}
app/templates/Base.html
View file @
91782440
...
...
@@ -19,6 +19,7 @@
<!-- Link CCS here -->
<link
rel=
"stylesheet"
href=
"{{ url_for('static', filename='css/bootstrap.min.css') }}"
>
<link
rel=
"stylesheet"
href=
"{{ url_for('static', filename='css/bootstrap-select.min.css') }}"
>
<link
rel=
"stylesheet"
href=
"{{ url_for('static', filename='css/bootstrap-switches.css') }}"
>
<!-- Link JS here -->
<script
src=
"{{ url_for('static', filename='js/jquery-3.5.1.min.js') }}"
></script>
<script
src=
"{{ url_for('static', filename='js/bootstrap.bundle.min.js') }}"
></script>
...
...
app/templates/BasePage.html
View file @
91782440
...
...
@@ -13,6 +13,7 @@
</div>
<div
class=
"container"
>
{% include 'include/messages.html' %}
<!-- actual content -->
{% block content %}
{% endblock content %}
...
...
app/templates/DownloadAuth.html
0 → 100644
View file @
91782440
{% extends 'BasePage.html' %}
{% block content %}
<div
class=
"form-signin card rounded-0 border-0 shadow text-center"
>
<form
method=
"post"
>
{{ form.hidden_tag() }}
{{ form.password(class='form-control', placeholder='Passwort ...') }}
{{ form.submit(class='btn btn-primary btn-block') }}
</form>
</div>
<style>
.form-signin
{
width
:
100%
;
max-width
:
330px
;
padding
:
15px
;
margin
:
auto
;
}
</style>
{% endblock content %}
\ No newline at end of file
app/templates/Upload.html
View file @
91782440
...
...
@@ -12,7 +12,9 @@
{{ form.hidden_tag() }}
<div
class=
"card-header"
>
<h4
class=
"my-0 font-weight-normal"
>
{{ form.name(class='form-control', placeholder='Name vergeben (optional)', size=64, maxlength=64) }}
</h4>
<h4
class=
"my-0 font-weight-normal"
>
{{ form.name(class='form-control', placeholder='Name vergeben (optional)', size=64, maxlength=64) }}
</h4>
</div>
<div
class=
"card-body"
>
...
...
@@ -30,10 +32,19 @@
<div
class=
"progress-bar"
role=
"progressbar"
aria-valuemin=
"0"
aria-valuemax=
"100"
></div>
</div>
<div
class=
"row"
>
<p
class=
"custom-control custom-switch col"
>
{{ form.is_public(class='custom-control-input') }}
{{ form.is_public.label(class='custom-control-label') }}
</p>
<div
class=
"col"
>
{{ form.password(class='form-control', placeholder='Passwort ...') }}
</div>
</div>
<div
class=
"form-group"
>
{{ form.submit(class='btn btn-lg btn-block btn-primary') }}
<button
type=
"button"
class=
"btn btn-lg btn-block btn-secondary d-none"
id=
"abort"
>
Abbruch
</button>
</div>
</div>
...
...
@@ -72,9 +83,6 @@
document
.
getElementById
(
'
submit
'
).
disabled
=
true
;
document
.
getElementById
(
'
name
'
).
disabled
=
true
;
document
.
getElementById
(
'
files
'
).
disabled
=
true
;
console
.
log
(
xhr
.
responseText
);
$
(
'
#abort
'
).
removeClass
(
'
d-none
'
).
click
(
function
(){
console
.
log
(
'
ABORTED UPLOAD
'
)
abb
=
true
...
...
@@ -86,14 +94,11 @@
$
(
'
.progress-bar
'
).
html
(
percentVal
).
width
(
percentVal
);
},
complete
:
function
(
xhr
)
{
console
.
log
(
xhr
.
responseText
);
if
(
!
abb
){
if
(
xhr
.
responseText
===
"
undefined
"
)
{
window
.
location
.
href
=
"
{{ url_for('expired') }}
"
}
else
if
(
xhr
.
responseText
.
includes
(
"
<html
"
))
{
$
(
"
html
"
).
html
(
xhr
.
responseText
);
$
(
"
html
"
).
html
(
xhr
.
responseText
);
// TODO JAVASCRIPT BROKKEN WENN MAN GANZE HATEMAIL replaced
}
else
{
window
.
location
.
href
=
xhr
.
responseText
;
}
...
...
app/templates/include/messages.html
0 → 100644
View file @
91782440
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
{# category being: (lowercase!!!)
success
info
warning
danger
primary
secondary
dark
#}
<div
class=
"alert alert-{{ category }} alert-dismissible fade show"
>
<button
type=
"button"
class=
"close"
data-dismiss=
"alert"
>
×
</button>
{{ message }}
</div>
{% endfor %}
{% endwith %}
\ No newline at end of file
docker-compose.yml
View file @
91782440
...
...
@@ -3,17 +3,19 @@ version: "3.8"
services
:
redis
:
container_name
:
"
chiefsend-redis"
hostname
:
"
chiefsend-
redis"
hostname
:
"
redis"
image
:
"
redis:latest"
rq
:
container_name
:
"
chiefsend-tasks"
image
:
"
chiefsend:latest"
entrypoint
:
rq worker --with-scheduler --url redis://
chiefsend-
redis
entrypoint
:
rq worker --with-scheduler --url redis://redis
environment
:
-
REDIS_URI=redis://
/chiefsend-
redis
-
REDIS_URI=redis://redis
depends_on
:
-
redis
links
:
-
redis
volumes
:
-
type
:
bind
source
:
./data
...
...
@@ -24,7 +26,7 @@ services:
build
:
.
image
:
"
chiefsend:latest"
environment
:
-
REDIS_URI=redis://
/chiefsend-
redis
-
REDIS_URI=redis://redis
deploy
:
resources
:
limits
:
...
...
@@ -38,6 +40,9 @@ services:
# - type: volume
# source: chiefsend-vol
# target: /home/chiefsend/data
links
:
-
redis
-
rq
depends_on
:
-
rq
-
nginx
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment