ticketfrei/frontend/website.py

284 lines
8.7 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
2018-01-08 21:56:05 +00:00
import os
2018-01-18 08:39:06 +00:00
import base64
import bottle
import sqlite3
2018-01-08 00:16:34 +00:00
import sendmail
2018-01-08 21:56:05 +00:00
import pytoml as toml
import jwt
import pylibscrypt
import smtplib
2018-03-16 13:21:15 +00:00
from bottle_auth import AuthPlugin
2018-01-08 21:56:05 +00:00
class Datagetter(object):
def __init__(self):
self.db = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "ticketfrei.sqlite")
self.conn = self.create_connection(self.db)
self.cur = self.conn.cursor()
def create_connection(self, db_file):
""" create a database connection to the SQLite database
specified by the db_file
:param db_file: database file
:return: Connection object or None
"""
try:
conn = sqlite3.connect(db_file)
return conn
except sqlite3.Error as e:
print(e)
return None
2018-01-18 08:39:06 +00:00
app = application = bottle.Bottle()
2018-01-18 08:39:06 +00:00
@app.route('/login', method="POST")
def login():
"""
Login to the ticketfrei account with credentials from the user table.
:return: bot.py Session Cookie
"""
uname = bottle.request.forms.get('uname')
psw = bottle.request.forms.get('psw')
2018-01-18 08:39:06 +00:00
psw = psw.encode("utf-8")
db.cur.execute("SELECT pass_hashed FROM user WHERE email=?;", (uname, ))
try:
pass_hashed = db.cur.fetchone()[0]
except TypeError:
return "Wrong Credentials." # no user with this email
if pylibscrypt.scrypt_mcf_check(pass_hashed, psw):
bottle.response.set_cookie("account", uname, secret)
return bottle.redirect("/settings")
else:
return "Wrong Credentials." # passphrase is wrong
2018-01-18 08:39:06 +00:00
@app.route('/register', method="POST")
def register():
"""
Login to the ticketfrei account with credentials from the user table.
:return: bot.py Session Cookie
"""
2018-01-08 00:16:34 +00:00
email = bottle.request.forms.get('email')
psw = bottle.request.forms.get('psw')
pswrepeat = bottle.request.forms.get('psw-repeat')
if pswrepeat != psw:
return "ERROR: Passwords don't match. Try again."
# check if email is already in use
db.cur.execute("SELECT id FROM user WHERE email=?;", (email,))
if db.cur.fetchone() is not None:
return "E-Mail is already in use." # account already exists
# hash and format for being encoded in the confirmation mail
2018-01-18 08:39:06 +00:00
psw = psw.encode("utf-8")
pass_hashed = pylibscrypt.scrypt_mcf(psw) # hash password
pass_hashed = base64.encodebytes(pass_hashed)
pass_hashed = pass_hashed.decode("ascii")
payload = {"email": email, "pass_hashed": pass_hashed}
2018-03-16 13:21:15 +00:00
# create confirm_link
encoded_jwt = jwt.encode(payload, secret).decode('utf-8')
2018-03-16 13:21:15 +00:00
confirm_link = "http://" + bottle.request.get_header('host') + "/confirm/" + str(encoded_jwt) # :todo http -> https
# send the mail
2018-01-08 00:16:34 +00:00
m = sendmail.Mailer(config)
try:
2018-03-16 13:21:15 +00:00
m.send("Complete your registration here: " + confirm_link, email, "[Ticketfrei] Confirm your account")
except smtplib.SMTPRecipientsRefused:
return "Please enter a valid E-Mail address."
2018-01-08 00:16:34 +00:00
return "We sent you an E-Mail. Please click on the confirmation link."
@app.route('/confirm/<encoded_jwt>', method="GET")
2018-03-16 13:21:15 +00:00
def confirm_account(encoded_jwt):
2018-01-08 00:16:34 +00:00
"""
Confirm the account creation and create a database entry.
:return: Redirection to bot.html
"""
# get values from URL
2018-03-16 13:21:15 +00:00
payload = jwt.decode(encoded_jwt, secret)
email = payload["email"]
pass_hashed = base64.b64decode(payload["pass_hashed"])
print(email, pass_hashed)
# create db entry
2018-03-16 13:21:15 +00:00
db.cur.execute("INSERT INTO user(email, pass_hashed, enabled) VALUES(?, ?, ?);", (email, pass_hashed, 1))
db.conn.commit()
2018-03-16 13:21:15 +00:00
bottle.response.set_cookie("account", email, secret)
2018-03-16 11:41:34 +00:00
bottle.response.set_cookie("enabled", "True")
return bottle.redirect("/settings")
@app.route('/settings')
def manage_bot():
"""
Restricted area. Deliver the bot settings page.
:return:
"""
uname = bottle.request.get_cookie("account", secret=secret)
if uname is not None:
2018-03-16 11:41:34 +00:00
db.cur.execute("SELECT enabled FROM user WHERE email=?;", (uname,))
try:
enabled = db.cur.fetchone()[0]
except TypeError:
return "Wrong Credentials." # no user with this email
# Set Enable Status with a Cookie
if enabled:
bottle.response.set_cookie("enabled", "True")
else:
bottle.response.set_cookie("enabled", "False")
return bottle.static_file("../static/bot.html", root='../static')
else:
bottle.abort(401, "Sorry, access denied.")
2018-01-08 00:16:34 +00:00
2018-03-16 14:17:12 +00:00
@app.route('/settings/goodlist', method="POST")
def update_goodlist():
"""
Writes the goodlist textarea on /settings to the database.
This function expects a multi-line string, transmitted over the textarea form.
:return: redirect to settings page
"""
# get new goodlist
words = bottle.request.forms.get("goodlist")
# get user.id
email = bottle.cookie_decode("account", secret)
db.cur.execute("SELECT id FROM user WHERE email = ?", (email, ))
user_id = db.cur.fetchone()
# write new goodlist to db
db.cur.execute("UPDATE trigger_good SET ? WHERE user.id = ?", (words, user_id, ))
return bottle.redirect("/settings")
@app.route('/settings/blacklist', method="POST")
def update_blacklist():
"""
Writes the blacklist textarea on /settings to the database.
This function expects a multi-line string, transmitted over the textarea form.
:return: redirect to settings page
"""
# get new blacklist
words = bottle.request.forms.get("blacklist")
# get user.id
email = bottle.cookie_decode("account", secret)
db.cur.execute("SELECT id FROM user WHERE email = ?", (email, ))
user_id = db.cur.fetchone()
# write new goodlist to db
db.cur.execute("UPDATE trigger_bad SET ? WHERE user.id = ?", (words, user_id, ))
return bottle.redirect("/settings")
2018-03-16 13:21:15 +00:00
2018-03-16 11:41:34 +00:00
@app.route('/enable', method="POST")
def enable():
2018-03-16 11:41:34 +00:00
"""
Enable the bot. Called by the Enable button in bot.html
:return: redirect to settings page
"""
email = bottle.request.get_cookie("account", secret=secret)
2018-03-16 13:21:15 +00:00
db.cur.execute("UPDATE user SET enabled = 1 WHERE email=?;", (email,))
db.conn.commit()
2018-03-16 11:41:34 +00:00
bottle.response.set_cookie("enabled", "True")
return bottle.redirect("/settings")
2018-03-16 13:21:15 +00:00
2018-03-16 11:41:34 +00:00
@app.route('/disable', method="POST")
def disable():
"""
Disable the bot. Called by the Disable button in bot.html
:return: redirect to settings page
"""
email = bottle.request.get_cookie("account", secret=secret)
2018-03-16 13:21:15 +00:00
db.cur.execute("UPDATE user SET enabled = 0 WHERE email=?;", (email,))
2018-03-16 11:41:34 +00:00
db.conn.commit()
bottle.response.set_cookie("enabled", "False")
return bottle.redirect("/settings")
2018-03-16 13:21:15 +00:00
@app.route('/login/twitter', method="POST")
def login_twitter():
"""
Starts the twitter OAuth authentication process.
:return: redirect to twitter.
"""
# twitter.redirect("no environ", "no cookie monster")
return "logging in with twitter is not implemented yet."
@app.route('/login/twitter/callback', method="POST")
def twitter_callback():
"""
Gets the callback
:return:
"""
return "logging in with twitter is not implemented yet."
@app.route('/login/mastodon', method="POST")
def login_mastodon():
"""
Starts the mastodon OAuth authentication process.
:return: redirect to twitter.
"""
# instance_url = bottle.request.forms.get('instance_url')
return "logging in with mastodon is not implemented yet."
@app.route('/static/<filename:path>')
def static(filename):
"""
Serve static files
"""
2018-02-17 11:31:49 +00:00
if filename == "bot.html":
bottle.abort(401, "Sorry, access denied.")
return bottle.static_file(filename, root='../static')
@app.route('/')
def show_index():
"""
The front "index" page
:return: /static/index.html
"""
return bottle.static_file("../static/index.html", root='../static')
class StripPathMiddleware(object):
"""
Get that slash out of the request
"""
def __init__(self, a):
self.a = a
def __call__(self, e, h):
e['PATH_INFO'] = e['PATH_INFO'].rstrip('/')
return self.a(e, h)
if __name__ == "__main__":
2018-01-26 16:54:11 +00:00
global config
2018-01-08 21:56:05 +00:00
with open('../config.toml') as configfile:
config = toml.load(configfile)
global db
2018-01-08 21:56:05 +00:00
global secret
2018-03-16 13:21:15 +00:00
global twitter
2018-01-08 21:56:05 +00:00
secret = os.urandom(32)
db = Datagetter()
2018-03-16 13:21:15 +00:00
host = '0.0.0.0'
from bottle_auth.social import twitter as twitterplugin
callback_url = host + '/login/twitter/callback'
twitter = twitterplugin.Twitter(config['tapp']['consumer_key'], config['tapp']['consumer_secret'], callback_url)
bottle.install(AuthPlugin(twitter))
try:
2018-03-16 13:21:15 +00:00
bottle.run(app=StripPathMiddleware(app), host=host, port=8080)
finally:
db.conn.close()