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.

242 lines
7.1 KiB

6 years ago
import re
import json
import pytz
6 years ago
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
6 years ago
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()
6 years ago
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)
6 years ago
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))
6 years ago
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'])
6 years ago
def register():
return render_template('register.html')
@journalmarks.route('/register', methods=['POST'])
def process_registration():
print(request.json)
if len(request.json) != 3:
6 years ago
return ('wrong number of fields', 400, None)
try:
token = request.json['token']
username = request.json['username']
password_hash = request.json['password_hash']
6 years ago
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()
6 years ago
u.save()
at.save()
return json.dumps('ok')
@journalmarks.route('/login', methods=['GET'])
6 years ago
def show_login():
if 'next' in request.args and urlparse(request.args['next']).netloc == '':
next = request.args['next']
else:
next = url_for('journalmarks.index')
6 years ago
return render_template('login.html', next=next)
@journalmarks.route('/login', methods=['POST'])
6 years ago
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')
6 years ago
def logout():
if 'username' in session:
del session['username']
6 years ago
return render_template('logout.html')
@journalmarks.route('/')
6 years ago
@login_required
def index():
return render_template('index.html')
@journalmarks.route('/create', methods=['POST'])
6 years ago
@login_required
def create():
6 years ago
if len(request.json) != 1 or 'content' not in request.json:
6 years ago
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)
6 years ago
@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'])
6 years ago
@login_required
def overview():
return render_template('overview.html')
@journalmarks.route('/overview', methods=['POST'])
6 years ago
@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
})
6 years ago
return json.dumps(ret)
@journalmarks.route('/<tag>')
6 years ago
@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')
6 years ago
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