Commit 91782440 authored by Lukas Böhm's avatar Lukas Böhm 💬
Browse files

auth, public and remove shared view

parent 970ad0ef
......@@ -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
......@@ -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 *
......
......@@ -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 = PasswordField('Passwort')
password = StringField('Passwort')
submit = SubmitField('Hochladen')
class AuthForm(FlaskForm):
password = PasswordField('Passwort', validators=[DataRequired()])
submit = SubmitField('Zugriff!')
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('shared', share_id=share.id)
return render_template('Shared.html', share=share, url=url_for('download', 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)
/*
* ==========================================
* 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;
}
......@@ -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>
......
......@@ -13,6 +13,7 @@
</div>
<div class="container">
{% include 'include/messages.html' %}
<!-- actual content -->
{% block content %}
{% endblock content %}
......
{% 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
......@@ -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;
}
......
{% 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">&times;</button>
{{ message }}
</div>
{% endfor %}
{% endwith %}
\ No newline at end of file
......@@ -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
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment