beautify, hashed passwords and session ids
This commit is contained in:
parent
fbe71b389e
commit
7e4a7ad38f
5 changed files with 122 additions and 27 deletions
81
raincloud.py
81
raincloud.py
|
|
@ -1,7 +1,11 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import crypt
|
||||||
import toml
|
import toml
|
||||||
|
import uuid
|
||||||
import werkzeug
|
import werkzeug
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from hmac import compare_digest as compare_hash
|
||||||
from flask import (
|
from flask import (
|
||||||
Flask,
|
Flask,
|
||||||
render_template,
|
render_template,
|
||||||
|
|
@ -16,14 +20,46 @@ from werkzeug.utils import secure_filename
|
||||||
|
|
||||||
base_path = Path("public")
|
base_path = Path("public")
|
||||||
cloud_name = "raincloud"
|
cloud_name = "raincloud"
|
||||||
|
sessions = []
|
||||||
|
|
||||||
|
# mkpass = "$6$7lTWfEYgx.nZdM.C$jb2cQt30FzpnEibp2sN2juGL0sGT2Y9dGlVTQvqxBB579Yy5lfbt3tIHjKYnt/MIcff6I6AFp8Q5k1xjN9C8a0"
|
||||||
|
# print(compare_hash(mkpass, crypt.crypt("test", mkpass)))
|
||||||
|
# exit(1)
|
||||||
|
|
||||||
|
|
||||||
class MincloudIOException(Exception):
|
class RaincloudIOException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def clean_sessions():
|
||||||
|
global sessions
|
||||||
|
sessions = [s for s in sessions if s[0] > datetime.now() - timedelta(days=1)]
|
||||||
|
|
||||||
|
|
||||||
|
def validate_session(directory, id_):
|
||||||
|
global sessions
|
||||||
|
valid_dates = [s[0] for s in sessions if s[1] == directory and s[2] == id_]
|
||||||
|
if len(valid_dates) > 0 and valid_dates[0] > datetime.now() - timedelta(days=1):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def delete_session(id_):
|
||||||
|
global sessions
|
||||||
|
sessions = [s for s in sessions if s[2] != id_]
|
||||||
|
|
||||||
|
|
||||||
|
def get_session_id():
|
||||||
|
global sessions
|
||||||
|
ids = [s[2] for s in sessions]
|
||||||
|
id_ = uuid.uuid4()
|
||||||
|
while id_ in ids:
|
||||||
|
id_ = uuid.uuid4()
|
||||||
|
return id_
|
||||||
|
|
||||||
|
|
||||||
def get_config(directory):
|
def get_config(directory):
|
||||||
"""Load a 'mincloud.conf' file from given directory.
|
"""Load a 'rc.toml' file from given directory.
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
directory - basepath of 'mincloud.conf'
|
directory - basepath of 'mincloud.conf'
|
||||||
|
|
@ -37,8 +73,10 @@ def get_config(directory):
|
||||||
config["directory"] = directory
|
config["directory"] = directory
|
||||||
|
|
||||||
parsed_config = toml.load(path)
|
parsed_config = toml.load(path)
|
||||||
config["password"] = (
|
config["hashed_password"] = (
|
||||||
parsed_config["password"] if "password" in parsed_config else None
|
parsed_config["hashed_password"]
|
||||||
|
if "hashed_password" in parsed_config
|
||||||
|
else None
|
||||||
)
|
)
|
||||||
config["download"] = (
|
config["download"] = (
|
||||||
parsed_config["download"] if "download" in parsed_config else False
|
parsed_config["download"] if "download" in parsed_config else False
|
||||||
|
|
@ -49,7 +87,7 @@ def get_config(directory):
|
||||||
return config
|
return config
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise MincloudIOException("No raincloud directory")
|
raise RaincloudIOException("No raincloud directory")
|
||||||
|
|
||||||
|
|
||||||
def get_files(directory):
|
def get_files(directory):
|
||||||
|
|
@ -68,16 +106,37 @@ app = Flask(__name__)
|
||||||
@app.route("/<directory>", methods=["GET", "POST"])
|
@app.route("/<directory>", methods=["GET", "POST"])
|
||||||
@app.route("/<directory>/<path:filename>", methods=["GET"])
|
@app.route("/<directory>/<path:filename>", methods=["GET"])
|
||||||
def directory(directory, filename=None):
|
def directory(directory, filename=None):
|
||||||
|
global sessions
|
||||||
try:
|
try:
|
||||||
|
|
||||||
|
# Clean sessions
|
||||||
|
clean_sessions()
|
||||||
|
|
||||||
|
# Logout
|
||||||
|
if request.method == "POST" and "logout" in request.form:
|
||||||
|
delete_session(session[directory])
|
||||||
|
return redirect(url_for("directory", directory=directory))
|
||||||
|
|
||||||
config = get_config(directory)
|
config = get_config(directory)
|
||||||
if config["password"]:
|
if config["hashed_password"]:
|
||||||
authenticated = True if directory in session and session[directory] == config["password"] else False
|
authenticated = (
|
||||||
|
True
|
||||||
|
if directory in session
|
||||||
|
and validate_session(directory, session[directory])
|
||||||
|
else False
|
||||||
|
)
|
||||||
|
|
||||||
if not authenticated:
|
if not authenticated:
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
if request.form["password"] == config["password"]:
|
if compare_hash(
|
||||||
session[directory] = config["password"]
|
config["hashed_password"],
|
||||||
|
crypt.crypt(
|
||||||
|
request.form["password"], config["hashed_password"]
|
||||||
|
),
|
||||||
|
):
|
||||||
|
id_ = get_session_id()
|
||||||
|
session[directory] = id_
|
||||||
|
sessions.append((datetime.now(), directory, id_))
|
||||||
return redirect(url_for("directory", directory=directory))
|
return redirect(url_for("directory", directory=directory))
|
||||||
else:
|
else:
|
||||||
return render_template(
|
return render_template(
|
||||||
|
|
@ -112,7 +171,7 @@ def directory(directory, filename=None):
|
||||||
else:
|
else:
|
||||||
return "No upload allowed"
|
return "No upload allowed"
|
||||||
|
|
||||||
except MincloudIOException as e:
|
except RaincloudIOException as e:
|
||||||
print(e)
|
print(e)
|
||||||
return "No 404 file"
|
return "No 404 file"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,16 +9,18 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#header {
|
#header {
|
||||||
padding: 15px 30px;
|
padding: 0px 30px;
|
||||||
border-bottom: 1px solid lightgray;
|
border-bottom: 1px solid lightgray;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
min-height: 60px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cloud-name {
|
#cloud-name {
|
||||||
|
color: #28bcff;
|
||||||
|
margin-right: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#directory-name {
|
#directory-name {
|
||||||
|
|
@ -26,17 +28,23 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
#content {
|
#content {
|
||||||
margin: 50px 150px;
|
margin: 50px 0px;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file {
|
.file {
|
||||||
border-bottom: 1px solid lightgrey;
|
border-bottom: 1px solid lightgrey;
|
||||||
padding: 5px 15px;
|
padding: 0px 30px;
|
||||||
|
min-height: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
.button {
|
.button {
|
||||||
background-color: #28bcff;
|
background-color: #28bcff;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
@ -55,14 +63,36 @@ body {
|
||||||
background-color: #0cb3ff;
|
background-color: #0cb3ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.upload {
|
||||||
|
background-color: #ff814e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload:hover {
|
||||||
|
background-color: #ff7740;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout {
|
||||||
|
background-color: #ff4e62;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logout:hover {
|
||||||
|
background-color: #ff3e54;
|
||||||
|
}
|
||||||
|
|
||||||
input[type="file"] {
|
input[type="file"] {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload {
|
input[type="password"] {
|
||||||
background-color: #4bcd8f;
|
border: 1px solid lightgrey;
|
||||||
|
padding: 11px 11px;
|
||||||
|
font-size: 16px;
|
||||||
|
border-radius: 5px;
|
||||||
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.upload:hover {
|
input[type="password"]:focus {
|
||||||
background-color: #3bc885;
|
outline: none;
|
||||||
|
border: 2px solid #ff814e;
|
||||||
|
padding: 10px 10px;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<form action="/{{ config["directory"] }}" method="post">
|
<form action="/{{ config["directory"] }}" method="post">
|
||||||
<input type="password" name="password">
|
<input type="password" name="password" placeholder="Password">
|
||||||
<input type="submit">
|
<input type="submit" class="button" value="Authenticate">
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,11 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||||
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.png') }}">
|
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.png') }}">
|
||||||
<title>{{ cloud_name }} :: {{ config["directory"] }}</title>
|
<title>{{ config["directory"] }} | {{ cloud_name }}</title>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<div>
|
<div>
|
||||||
<span id="cloud-name">{{ cloud_name }} :: </span><span id="directory-name">{{ config["directory"] }}</span>
|
<span id="cloud-name">{{ cloud_name }}</span><span id="directory-name">{{ config["directory"] }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% block nav_content %}{% endblock %}
|
{% block nav_content %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,21 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block nav_content %}
|
{% block nav_content %}
|
||||||
{% if config["upload"] %}
|
<div>
|
||||||
<div>
|
{% if config["upload"] %}
|
||||||
<form action="/{{ config["directory"] }}" enctype="multipart/form-data" method="post">
|
<form action="/{{ config["directory"] }}" enctype="multipart/form-data" method="post">
|
||||||
<label class="button upload">
|
<label class="button upload">
|
||||||
<input type="file" name="file" onchange="this.form.submit()">
|
<input type="file" name="file" onchange="this.form.submit()">
|
||||||
Upload
|
Upload
|
||||||
</label>
|
</label>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
{% endif %}
|
||||||
{% endif %}
|
{% if config["hashed_password"] %}
|
||||||
|
<form action="/{{ config["directory"] }}" method="post">
|
||||||
|
<input hidden type="text" name="logout">
|
||||||
|
<input type="submit" class="button logout" value="Logout">
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
{% for f in files %}
|
{% for f in files %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue