A Minimalistic and Privary-by-default URL sortener
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

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