from bottle import redirect, request from functools import wraps from inspect import Signature import jwt import logging from os import urandom from pylibscrypt import scrypt_mcf, scrypt_mcf_check import sqlite3 from user import User logger = logging.getLogger(__name__) class DB(object): def __init__(self, dbfile): self.conn = sqlite3.connect(dbfile) self.cur = self.conn.cursor() self.create() self.secret = urandom(32) def create(self): # init db self.cur.executescript(''' CREATE TABLE IF NOT EXISTS user ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, email TEXT, passhash TEXT, enabled INTEGER DEFAULT 1 ); CREATE TABLE IF NOT EXISTS twitter_request_tokens ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, request_token TEXT, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS twitter_accounts ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, client_id TEXT, client_secret TEXT, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS trigger_good ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, words TEXT, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS trigger_bad ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, words TEXT, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS mastodon_instances ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, instance TEXT, client_id TEXT, client_secret TEXT ); CREATE TABLE IF NOT EXISTS mastodon_accounts ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, access_token TEXT, instance_id TEXT, active INTEGER, FOREIGN KEY(user_id) REFERENCES user(id), FOREIGN KEY(instance_id) REFERENCES mastodon_instances(id) ); CREATE TABLE IF NOT EXISTS seen_toots ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, mastodon_accounts_id INTEGER, toot_id TEXT, FOREIGN KEY(user_id) REFERENCES user(id), FOREIGN KEY(mastodon_accounts_id) REFERENCES mastodon_accounts(id) ); CREATE TABLE IF NOT EXISTS mail ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, email TEXT, active INTEGER, FOREIGN KEY(user_id) REFERENCES user(id) ); CREATE TABLE IF NOT EXISTS seen_mails ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, mail_id INTEGER, mail_date INTEGER, FOREIGN KEY(user_id) REFERENCES user(id), FOREIGN KEY(mail_id) REFERENCES mail(id) ); CREATE TABLE IF NOT EXISTS seen_tweets ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE, user_id INTEGER, twitter_accounts_id INTEGER, tweet_id TEXT, FOREIGN KEY(user_id) REFERENCES user(id) FOREIGN KEY(twitter_accounts_id) REFERENCES twitter_accounts(id) ); ''') def token(self, email, password): return jwt.encode({ 'email': email, 'passhash': scrypt_mcf(password.encode('utf-8')).decode('ascii') }, self.secret).decode('ascii') def register(self, token): json = jwt.decode(token, self.secret) # create user self.cur.execute("INSERT INTO user (email, passhash) VALUES(?, ?);", (json['email'], json['passhash'])) self.conn.commit() return User(self, self.cur.lastrowid) def authenticate(self, email, password): # check email/password self.cur.execute("SELECT id, passhash FROM user WHERE email=?;", (email, )) row = self.cur.fetchone() if not row: return None # No user with this email if not scrypt_mcf_check(row[1].encode('ascii'), password.encode('utf-8')): return None # Wrong passphrase return User(self, row[0]) def by_email(self, email): self.cur.execute("SELECT id FROM user WHERE email=?;", (email, )) row = self.cur.fetchone() if not row: return None return User(self, row[0]) def close(self): self.conn.close() def get_users(self): self.cur.execute("SELECT id FROM user WHERE enabled=1;") return self.cur.fetchall() class DBPlugin(object): name = 'DBPlugin' api = 2 def __init__(self, dbfile, loginpage): self.db = DB(dbfile) self.loginpage = loginpage def close(self): self.db.close() def apply(self, callback, route): uservar = route.config.get('user', None) dbvar = route.config.get('db', None) signature = Signature.from_callable(route.callback) @wraps(callback) def wrapper(*args, **kwargs): if uservar and uservar in signature.parameters: uid = request.get_cookie('uid', secret=self.db.secret) if uid is None: return redirect(self.loginpage) kwargs[uservar] = User(self.db, uid) if dbvar and dbvar in signature.parameters: kwargs[dbvar] = self.db return callback(*args, **kwargs) return wrapper