You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
241 lines
7.1 KiB
241 lines
7.1 KiB
|
|
import re |
|
import json |
|
import pytz |
|
import random |
|
import string |
|
import hashlib |
|
import datetime |
|
import functools |
|
from urllib.parse import urlparse |
|
from flask import Flask, request, session, render_template, url_for, \ |
|
redirect, Blueprint |
|
from flask import g as flask_g |
|
from peewee import CharField, DateTimeField, ForeignKeyField, DoesNotExist |
|
from playhouse.flask_utils import FlaskDB |
|
from passlib.hash import pbkdf2_sha256 |
|
|
|
|
|
journalmarks = Blueprint('journalmarks', __name__, template_folder='templates', |
|
static_folder='static') |
|
db_wrapper = FlaskDB() |
|
|
|
|
|
class User(db_wrapper.Model): |
|
username = CharField(unique=True) |
|
password = CharField() |
|
|
|
|
|
class AccessToken(db_wrapper.Model): |
|
token = CharField() |
|
redeemed = DateTimeField(null=True, default=None) |
|
|
|
|
|
class Journalmark(db_wrapper.Model): |
|
user = ForeignKeyField(User, backref='journalmarks') |
|
created = DateTimeField(default=datetime.datetime.utcnow) |
|
tag = CharField(unique=True) |
|
content = CharField() |
|
|
|
|
|
def login_required(f): |
|
@functools.wraps(f) |
|
def decorated_function(*args, **kwargs): |
|
if 'username' not in session: |
|
return redirect(url_for('journalmarks.login', next=request.path)) |
|
try: |
|
u = User.select().where(User.username == session['username']).get() |
|
except DoesNotExist: |
|
return ('user not found', 400, None) |
|
flask_g.user = u |
|
return f(*args, **kwargs) |
|
return decorated_function |
|
|
|
|
|
@journalmarks.route('/register', methods=['GET']) |
|
def register(): |
|
return render_template('register.html') |
|
|
|
@journalmarks.route('/register', methods=['POST']) |
|
def process_registration(): |
|
print(request.json) |
|
if len(request.json) != 3: |
|
return ('wrong number of fields', 400, None) |
|
try: |
|
token = request.json['token'] |
|
username = request.json['username'] |
|
password_hash = request.json['password_hash'] |
|
except ValueError: |
|
return ('invalid field names', 400, None) |
|
if not re.match('^[a-zA-Z0-9]*$', token): |
|
return ('invalid token', 400, None) |
|
if not re.match('^[a-zA-Z0-9._]*$', username): |
|
return ('invalid username', 400, None) |
|
if not re.match('^[a-f0-9]{64}$', password_hash): |
|
return ('invalid password hash', 400, None) |
|
password_hash = pbkdf2_sha256.encrypt(password_hash) |
|
|
|
try: |
|
at = AccessToken.select().where(AccessToken.token == token).get() |
|
except DoesNotExist: |
|
return ('token does not exist', 400, None) |
|
|
|
if at.redeemed is not None: |
|
return ('token already used', 400, None) |
|
|
|
if User.select().where(User.username == username).count() > 0: |
|
return ('user already exists', 400, None) |
|
|
|
with db_wrapper.database.atomic(): |
|
u = User(username=username, password=password_hash) |
|
at.redeemed = datetime.datetime.utcnow() |
|
u.save() |
|
at.save() |
|
return json.dumps('ok') |
|
|
|
|
|
@journalmarks.route('/login', methods=['GET']) |
|
def show_login(): |
|
if 'next' in request.args and urlparse(request.args['next']).netloc == '': |
|
next = request.args['next'] |
|
else: |
|
next = url_for('journalmarks.index') |
|
return render_template('login.html', next=next) |
|
|
|
|
|
@journalmarks.route('/login', methods=['POST']) |
|
def login(): |
|
print(request.json) |
|
if len(request.json) != 2: |
|
return ('wrong number of fields', 400, None) |
|
try: |
|
username = request.json['username'] |
|
password_hash = request.json['password_hash'] |
|
except ValueError: |
|
return ('invalid field names', 400, None) |
|
if not re.match('^[a-zA-Z0-9._]*$', username): |
|
return ('invalid username', 400, None) |
|
if not re.match('^[a-f0-9]{64}$', password_hash): |
|
return ('invalid password hash', 400, None) |
|
|
|
try: |
|
u = User.select().where(User.username == username).get() |
|
except DoesNotExist: |
|
return ('invalid credentials', 400, None) |
|
if not pbkdf2_sha256.verify(password_hash, u.password): |
|
return ('invalid credentials', 400, None) |
|
|
|
session['username'] = u.username |
|
session.permanent = True |
|
return json.dumps('ok') |
|
|
|
|
|
@journalmarks.route('/logout') |
|
def logout(): |
|
if 'username' in session: |
|
del session['username'] |
|
return render_template('logout.html') |
|
|
|
|
|
@journalmarks.route('/') |
|
@login_required |
|
def index(): |
|
return render_template('index.html') |
|
|
|
|
|
@journalmarks.route('/create', methods=['POST']) |
|
@login_required |
|
def create(): |
|
if len(request.json) != 1 or 'content' not in request.json: |
|
return ('invalid fields', 400, None) |
|
content = request.json['content'] |
|
|
|
tag = None |
|
while tag is None: |
|
tag = ''.join([ random.choice(string.ascii_lowercase + string.digits) \ |
|
for i in range(4) ]) |
|
if Journalmark.select().where(Journalmark.tag == tag).count() > 0: |
|
tag = None |
|
j = Journalmark(user=flask_g.user, tag=tag, content=json.dumps(content)) |
|
j.save() |
|
return json.dumps(tag) |
|
|
|
|
|
@journalmarks.route('/delete', methods=['POST']) |
|
@login_required |
|
def delete(): |
|
if len(request.json) != 1 or 'tag' not in request.json: |
|
return ('invalid fields', 400, None) |
|
tag = request.json['tag'] |
|
try: |
|
j = Journalmark.select().where( |
|
(Journalmark.tag == tag) & (Journalmark.user == flask_g.user) |
|
).get() |
|
j.delete_instance() |
|
except DoesNotExist: |
|
return ('tag not found', 404, None) |
|
return json.dumps('ok') |
|
|
|
|
|
@journalmarks.route('/update', methods=['POST']) |
|
@login_required |
|
def update(): |
|
if request.json.keys() != {'tag', 'content'}: |
|
return ('invalid fields', 400, None) |
|
tag, content = request.json['tag'], request.json['content'] |
|
try: |
|
j = Journalmark.select().where( |
|
(Journalmark.tag == tag) & (Journalmark.user == flask_g.user) |
|
).get() |
|
except DoesNotExist: |
|
return ('tag not found', 404, None) |
|
j.content = json.dumps(content) |
|
j.save() |
|
return json.dumps('ok') |
|
|
|
|
|
@journalmarks.route('/overview', methods=['GET']) |
|
@login_required |
|
def overview(): |
|
return render_template('overview.html') |
|
|
|
|
|
@journalmarks.route('/overview', methods=['POST']) |
|
@login_required |
|
def overview_get_journalmarks(): |
|
jms = Journalmark.select().where(Journalmark.user == flask_g.user) \ |
|
.order_by(Journalmark.created.desc()) |
|
ret = [] |
|
for j in jms: |
|
try: |
|
content = json.loads(j.content) |
|
except ValueError: |
|
content = None |
|
ret.append({ |
|
'created': pytz.utc.localize(j.created).isoformat(), |
|
'tag': j.tag, |
|
'content': content |
|
}) |
|
return json.dumps(ret) |
|
|
|
|
|
@journalmarks.route('/<tag>') |
|
@login_required |
|
def get_journalmark(tag): |
|
print(tag) |
|
try: |
|
j = Journalmark.select().where( |
|
(Journalmark.tag == tag) & (Journalmark.user == flask_g.user) |
|
).get() |
|
except DoesNotExist: |
|
return render_template('not_found.html') |
|
return render_template('get_journalmark.html', j=j) |
|
|
|
|
|
def create_app(config_filename): |
|
app = Flask(__name__) |
|
app.register_blueprint(journalmarks) |
|
app.config.from_pyfile(config_filename) |
|
app.secret_key = app.config['SECRET_KEY'] |
|
db_wrapper.init_app(app) |
|
return app
|
|
|