|
|
|
|
|
|
|
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
|