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('/') @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