diff --git a/README.md b/README.md index 40b3fe2..f080c91 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Last step – open your favourite Gemini client and jump into gemini://localhost Have fun! -## Screenshots (cooming soon) +## Screenshots (comming soon) ## License diff --git a/lib/auth/__init__.py b/lib/auth/__init__.py index 7dec30d..7a4bbd8 100644 --- a/lib/auth/__init__.py +++ b/lib/auth/__init__.py @@ -7,7 +7,7 @@ def dict_factory(cursor, row): fields = [column[0] for column in cursor.description] return {key: value for key, value in zip(fields, row)} -def generateToken(length): +def generate_token(length): import random token = '' for i in range(0, length): @@ -20,24 +20,24 @@ class auth: ANTIC_EXPIRE = 60*60*24 hash = None - certName = None + cert_name = None username = None anticsrf = False # User row cache. It's enough to ask database once and update cache only when asked for outdated or missing columns. user = {} - userOutdated = [] + user_outdated = [] # User keys cache indexed by hashes keys = {} - keysOutdated = ["all"] - # getKeys always returns all keys owned by user. + keys_outdated = ["all"] + # get_keys always returns all keys owned by user. - def __init__(self, dbFile): + def __init__(self, db_file): """ database auto-creation, garbage collection """ - self.con = sqlite3.connect(dbFile) + self.con = sqlite3.connect(db_file) self.con.row_factory = dict_factory if (DEBUG): self.con.set_trace_callback(logging.warning) @@ -68,37 +68,37 @@ class auth: """) # TODO: database migration - # self.migrateDatabase() - self.garbageCollector() + # self.migrate_database() + self.garbage_collector() - def garbageCollector(self): + def garbage_collector(self): """ delete all unlinked keys and expired tokens """ - # garbageCollector is intended to run before caching initialization + # garbage_collector is intended to run before caching initialization self.cur.execute("DELETE FROM keys WHERE user IS NULL") self.cur.execute("UPDATE users SET link_token = NULL, link_token_time = NULL WHERE link_token_time + ? - strftime('%s') <= 0", (self.LINK_EXPIRE, )) # field request_rename expire together with anticsrf self.cur.execute("UPDATE users SET anticsrf = NULL, anticsrf_time = NULL, request_rename = NULL WHERE anticsrf_time + ? - strftime('%s') <= 0", (self.ANTIC_EXPIRE, )) self.con.commit() - def passKey(self, hash, name=None): + def pass_key(self, hash, name=None): """ pass given key to the object """ self.hash = hash - self.certName = name + self.cert_name = name - key = self.fetchKey() + key = self.fetch_key() if (not key): - self.registerKey() + self.register_key() return # if key is just registered, there is not username yet self.username = key['username'] - self.updateKey() + self.update_key() # we do not need to update cache right now - def fetchKey(self): + def fetch_key(self): """ get current key and username """ @@ -115,7 +115,7 @@ class auth: return res - def getKeys(self, require=[]): + def get_keys(self, require=[]): """ get all keys of current user "require" argument controls which fields must be up to date - if ommited, there are no requirements! @@ -124,13 +124,13 @@ class auth: return None outdated = False - if ('all' not in self.keysOutdated): + if ('all' not in self.keys_outdated): for key in require: - if (key in self.keysOutdated): + if (key in self.keys_outdated): outdated = True break else: - self.keysOutdated.remove('all') + self.keys_outdated.remove('all') outdated = True if (outdated): @@ -141,13 +141,13 @@ class auth: for key in res: self.keys[key['hash']] = key del key['hash'] - self.keysOutdated.clear() + self.keys_outdated.clear() if (DEBUG): - logging.warning({"keys": self.keys, "keysOutdated": self.keysOutdated}) + logging.warning({"keys": self.keys, "keys_outdated": self.keys_outdated}) return self.keys - def userInfo(self, column): + def user_info(self, column): """ get user row from database/cache """ @@ -155,38 +155,38 @@ class auth: return None if (DEBUG): - logging.warning({"user": self.user, "userOutdated": self.userOutdated, "requested": column}) + logging.warning({"user": self.user, "user_outdated": self.user_outdated, "requested": column}) - if (column in self.user and column not in self.userOutdated): + if (column in self.user and column not in self.user_outdated): return self.user[column] res = self.cur.execute("SELECT * FROM users WHERE users.name = ?", (self.username, )) self.user = res.fetchone() - self.userOutdated.clear() + self.user_outdated.clear() return self.user[column] - def updateUserInfo(self, column, value): + def update_user_info(self, column, value): """ - handy function for quick update self.user and self.userOutdated - for outdating it's enough to self.userOutdated.append(key) + handy function for quick update self.user and self.user_outdated + for outdating it's enough to self.user_outdated.append(key) """ self.user[column] = value - while (column in self.userOutdated): # remove duplicates - self.userOutdated.remove(column) + while (column in self.user_outdated): # remove duplicates + self.user_outdated.remove(column) - def updateKeyInfo(self, hash, column, value): + def update_key_info(self, hash, column, value): """ - handy function (at the moment almost inevitable) for quick update self.keys and self.keysOutdated - for outdating it's enough to self.keysOutdated.append(key) + handy function (at the moment almost inevitable) for quick update self.keys and self.keys_outdated + for outdating it's enough to self.keys_outdated.append(key) """ if (hash not in self.keys): self.keys[hash] = {} self.keys[hash][column] = value - while (column in self.keysOutdated): # remove duplicates - self.keysOutdated.remove(column) + while (column in self.keys_outdated): # remove duplicates + self.keys_outdated.remove(column) - def genAnticSRF(self): + def gen_anticsrf(self): """ generate antic cross-site request forgery token There's one token per session. @@ -196,18 +196,18 @@ class auth: # skip generating token if already generated for this session if (self.anticsrf): - return self.userInfo('anticsrf') + return self.user_info('anticsrf') - token = generateToken(4) + token = generate_token(4) self.cur.execute("UPDATE users SET anticsrf = ?, anticsrf_time = strftime('%s') WHERE name = ?", (token, self.username)) self.con.commit() self.anticsrf = True - self.updateUserInfo('anticsrf', token) - self.userOutdated.append('anticsrf_time') + self.update_user_info('anticsrf', token) + self.user_outdated.append('anticsrf_time') return token - def checkAnticSRF(self, token): + def check_anticsrf(self, token): """ check antic cross-site request forgery token validity Remider: there's one token per session @@ -215,27 +215,27 @@ class auth: if (not self.username): return None - validity = token == self.userInfo('anticsrf') + validity = token == self.user_info('anticsrf') self.cur.execute("UPDATE users SET anticsrf = NULL, anticsrf_time = NULL WHERE name = ?", (self.username, )) self.con.commit() - self.updateUserInfo('anticsrf', None) - self.updateUserInfo('anticsrf_time', None) + self.update_user_info('anticsrf', None) + self.update_user_info('anticsrf_time', None) return validity - def registerKey(self): + def register_key(self): """ insert new key into database (will be deleted if not linked) """ if (not self.hash): return None - self.cur.execute("INSERT INTO keys (hash, name) VALUES (?, ?)", (self.hash, self.certName)) + self.cur.execute("INSERT INTO keys (hash, name) VALUES (?, ?)", (self.hash, self.cert_name)) # unlinked key is deleted anyway, so why don't wait with commit until linking and avoid unneccessary disk IO? - self.updateKeyInfo(self.hash, 'name', self.certName) + self.update_key_info(self.hash, 'name', self.cert_name) - def updateKey(self): + def update_key(self): """ touch current key timestamp """ @@ -244,7 +244,7 @@ class auth: self.cur.execute("UPDATE keys SET last_seen = strftime('%s') WHERE hash = ?", (self.hash, )) self.con.commit() - self.keysOutdated.append('last_seen') + self.keys_outdated.append('last_seen') SUCCESS = 0 NAME_IN_USE = 1 @@ -253,7 +253,7 @@ class auth: KEY_IN_USE = 4 NOT_FOUND = 5 - def registerUser(self, username): + def register_user(self, username): """ link new user to the current key """ @@ -273,11 +273,11 @@ class auth: # now the key is protected from autodeletion self.con.commit() self.username = username - self.updateKeyInfo(self.hash, 'user', uid) + self.update_key_info(self.hash, 'user', uid) return self.SUCCESS - def requestLink(self, cancel=False): + def request_link(self, cancel=False): """ generate link token """ @@ -285,7 +285,7 @@ class auth: return None if (cancel): - self.burnLink(self.username) + self.burn_link(self.username) self.con.commit() return True @@ -293,7 +293,7 @@ class auth: token = None while not token: - token = generateToken(16) + token = generate_token(16) res = self.cur.execute("SELECT * FROM users WHERE link_token = ?", (token, )) if (res.fetchone()): token = None @@ -303,18 +303,18 @@ class auth: self.cur.execute("UPDATE users SET link_token = ?, link_token_time = strftime('%s') WHERE name = ?", (token, self.username)) self.con.commit() - self.updateUserInfo('link_token', token) - self.userOutdated.append('link_token_time') + self.update_user_info('link_token', token) + self.user_outdated.append('link_token_time') return token - def burnLink(self, username): + def burn_link(self, username): """ force link token expiration """ self.cur.execute("UPDATE users SET link_token = NULL, link_token_time = NULL WHERE name = ?", (username, )) - self.updateUserInfo('link_token', None) - self.updateUserInfo('link_token_time', None) + self.update_user_info('link_token', None) + self.update_user_info('link_token_time', None) def link(self, token): """ @@ -327,9 +327,9 @@ class auth: res = res.fetchone() if (res): self.cur.execute("UPDATE keys SET user = ? WHERE hash = ?", (res['id'], self.hash)) - self.burnLink(res['name']) + self.burn_link(res['name']) self.con.commit() - self.updateKeyInfo(self.hash, 'user', res['id']) + self.update_key_info(self.hash, 'user', res['id']) self.username = res['name'] return self.SUCCESS else: @@ -345,7 +345,7 @@ class auth: if (hash == self.hash): return self.KEY_IN_USE - if (hash in self.getKeys()): + if (hash in self.get_keys()): self.cur.execute("DELETE FROM keys WHERE hash = ?", (hash, )) self.con.commit() del self.keys[hash] @@ -353,14 +353,14 @@ class auth: return self.NOT_FOUND - def requestRename(self, hash): + def request_rename(self, hash): """ prepare for changing name of given key """ if (not self.username): return None - if (hash in self.getKeys()): + if (hash in self.get_keys()): self.cur.execute("UPDATE users SET request_rename = ? WHERE name = ?", (hash, self.username)) self.con.commit() self.user['request_rename'] = hash @@ -368,21 +368,21 @@ class auth: return self.NOT_FOUND - def renameKey(self, name): + def rename_key(self, name): """ Change the display name of given key """ if (not self.username): return None - hash = self.userInfo('request_rename') + hash = self.user_info('request_rename') # key hash in database is probably good, but's better safe than sorry - if (hash in self.getKeys()): + if (hash in self.get_keys()): self.cur.execute("UPDATE keys SET name = ? WHERE hash = ?", (name, hash)) self.cur.execute("UPDATE users SET request_rename = NULL WHERE name = ?", (self.username, )) self.con.commit() - self.updateKeyInfo(hash, 'name', name) - self.updateUserInfo('request_rename', None) + self.update_key_info(hash, 'name', name) + self.update_user_info('request_rename', None) return self.SUCCESS return self.NOT_FOUND @@ -393,9 +393,9 @@ if (__name__ == '__main__'): if (len(sys.argv) > 1): auth(sys.argv[1]) print({ - "enableRegistration": auth.ENABLE_REGISTRATION, - "linkExpire": auth.LINK_EXPIRE, - "anticExpire": auth.ANTIC_EXPIRE, + "enable_registration": auth.ENABLE_REGISTRATION, + "link_expire": auth.LINK_EXPIRE, + "antic_expire": auth.ANTIC_EXPIRE, "debug": DEBUG }) else: diff --git a/public/cgi/account/index.gmi b/public/cgi/account/index.gmi index 3bdf44e..fba3707 100755 --- a/public/cgi/account/index.gmi +++ b/public/cgi/account/index.gmi @@ -10,13 +10,13 @@ if (not hash): # no CC print('60 Authentication is required\r\n') exit() -certName = os.environ.get('REMOTE_USER') +cert_name = os.environ.get('REMOTE_USER') print('20 text/gemini\r\n') from auth import auth auth = auth('data/data.db') -auth.passKey(hash, certName) +auth.pass_key(hash, cert_name) if (not auth.username): # mismatch @@ -29,10 +29,10 @@ else: print('Hello, {}!'.format(auth.username)) print('## Your keys') - myKeys = auth.getKeys(['last_seen']) - for hash in myKeys: - key = myKeys[hash] - lastSeen = datetime.fromtimestamp(key['last_seen']) + my_keys = auth.get_keys(['last_seen']) + for hash in my_keys: + key = my_keys[hash] + last_seen = datetime.fromtimestamp(key['last_seen']) current = hash == auth.hash name = key['name'] @@ -44,14 +44,14 @@ else: print(label) print('hash:', hash) - print('last seen:', lastSeen) - print('=> rename-request.gmi?{} rename'.format(auth.genAnticSRF() + hash)) + print('last seen:', last_seen) + print('=> rename-request.gmi?{} rename'.format(auth.gen_anticsrf() + hash)) if (not current): - print('=> unlink.gmi?{} unlink'.format(auth.genAnticSRF() + hash)) + print('=> unlink.gmi?{} unlink'.format(auth.gen_anticsrf() + hash)) - linkToken = auth.userInfo('link_token') - if(linkToken): - expire = datetime.fromtimestamp(auth.userInfo('link_token_time') + auth.LINK_EXPIRE) + link_token = auth.user_info('link_token') + if(link_token): + expire = datetime.fromtimestamp(auth.user_info('link_token_time') + auth.LINK_EXPIRE) delta = expire - datetime.now() minutes = delta.seconds // 60 seconds = delta.seconds % 60 @@ -59,7 +59,7 @@ else: def zero(n): return str(n) if n>10 else '0'+str(n) print('### Link new key') - print('Token {} will expire in {}:{}'.format(linkToken, zero(minutes), zero(seconds))) + print('Token {} will expire in {}:{}'.format(link_token, zero(minutes), zero(seconds))) print('=> link.gmi?cancel cancel') else: print('---') diff --git a/public/cgi/account/link.gmi b/public/cgi/account/link.gmi index 8fb8e1c..1a1c5e2 100755 --- a/public/cgi/account/link.gmi +++ b/public/cgi/account/link.gmi @@ -10,11 +10,11 @@ if (not hash): # no CC print('60 Authentication is required\r\n') exit() -certName = os.environ.get('REMOTE_USER') +cert_name = os.environ.get('REMOTE_USER') from auth import auth auth = auth('data/data.db') -auth.passKey(hash, certName) +auth.pass_key(hash, cert_name) query = os.environ.get('QUERY_STRING') @@ -24,7 +24,7 @@ if (auth.username): # empty from datetime import datetime - token = auth.requestLink() + token = auth.request_link() if (not token): print('40 Unknown error\r\n') exit() @@ -36,9 +36,9 @@ if (auth.username): print('=> ?cancel cancel') elif (query == 'cancel'): # cancel - auth.requestLink(cancel=True) + auth.request_link(cancel=True) print('30 index.gmi\r\n') - elif (query == auth.userInfo('link_token')): + elif (query == auth.user_info('link_token')): print('20 text/gemini\r\n') print('Tip: open this link on new device in order to link new key to your account') else: diff --git a/public/cgi/account/register.gmi b/public/cgi/account/register.gmi index 040c81e..a2d4a3f 100755 --- a/public/cgi/account/register.gmi +++ b/public/cgi/account/register.gmi @@ -10,11 +10,11 @@ if (not hash): # no CC print('60 Authentication is required\r\n') exit() -certName = os.environ.get('REMOTE_USER') +cert_name = os.environ.get('REMOTE_USER') from auth import auth auth = auth('data/data.db') -auth.passKey(hash, certName) +auth.pass_key(hash, cert_name) if (auth.username): # match @@ -33,7 +33,7 @@ else: print('10 Choose your name\r\n') else: # string - res = auth.registerUser(username) + res = auth.register_user(username) if (res == auth.SUCCESS): print('31 index.gmi\r\n') elif (res == auth.NAME_IN_USE): diff --git a/public/cgi/account/rename-request.gmi b/public/cgi/account/rename-request.gmi index 572457a..8b11e06 100755 --- a/public/cgi/account/rename-request.gmi +++ b/public/cgi/account/rename-request.gmi @@ -10,11 +10,11 @@ if (not hash): # no CC print('60 Authentication is required\r\n') exit() -certName = os.environ.get('REMOTE_USER') +cert_name = os.environ.get('REMOTE_USER') from auth import auth auth = auth('data/data.db') -auth.passKey(hash, certName) +auth.pass_key(hash, cert_name) if (not auth.username): # mismatch @@ -29,22 +29,22 @@ else: print('20 text/gemini\r\n') print('Which key would you like to rename?') - myKeys = auth.getKeys(['last_seen']) - for hash in myKeys: - key = myKeys['hash'] - lastSeen = datetime.fromtimestamp(key['last_seen']) + my_keys = auth.get_keys(['last_seen']) + for hash in my_keys: + key = my_keys['hash'] + last_seen = datetime.fromtimestamp(key['last_seen']) current = hash == auth.hash name = key['name'] name = '"' + name + '"' if name else '[no name]' - label = '=> rename-request.gmi?{} {}'.format(auth.genAnticSRF() + hash, name) + label = '=> rename-request.gmi?{} {}'.format(auth.gen_anticsrf() + hash, name) if (current): label += ' (currently used)' print(label) print('hash:', hash) - print('last seen:', lastSeen) + print('last seen:', last_seen) else: anticsrf = query[:4] hash = query[4:] @@ -54,8 +54,8 @@ else: exit() # anticsrf+hash - if (auth.checkAnticSRF(anticsrf)): - res = auth.requestRename(hash) + if (auth.check_anticsrf(anticsrf)): + res = auth.request_rename(hash) if (res == auth.SUCCESS): print('30 rename.gmi\r\n') elif (res == auth.NOT_FOUND): diff --git a/public/cgi/account/rename.gmi b/public/cgi/account/rename.gmi index 968ea66..27e3af6 100755 --- a/public/cgi/account/rename.gmi +++ b/public/cgi/account/rename.gmi @@ -10,11 +10,11 @@ if (not hash): # no CC print('60 Authentication is required\r\n') exit() -certName = os.environ.get('REMOTE_USER') +cert_name = os.environ.get('REMOTE_USER') from auth import auth auth = auth('data/data.db') -auth.passKey(hash, certName) +auth.pass_key(hash, cert_name) if (not auth.username): # mismatch @@ -24,14 +24,14 @@ else: name = os.environ.get('QUERY_STRING') if (not name): # empty - if (auth.userInfo('request_rename')): + if (auth.user_info('request_rename')): # TODO: tell which key are you renaming print('10 Choose new name for your key\r\n') else: print('30 rename-request.gmi\r\n') else: # string - res = auth.renameKey(name) + res = auth.rename_key(name) if (res == auth.SUCCESS): print('30 index.gmi\r\n') elif (res == auth.NOT_FOUND): diff --git a/public/cgi/account/unlink.gmi b/public/cgi/account/unlink.gmi index 14c6738..4287820 100755 --- a/public/cgi/account/unlink.gmi +++ b/public/cgi/account/unlink.gmi @@ -10,11 +10,11 @@ if (not hash): # no CC print('60 Authentication is required\r\n') exit() -certName = os.environ.get('REMOTE_USER') +cert_name = os.environ.get('REMOTE_USER') from auth import auth auth = auth('data/data.db') -auth.passKey(hash, certName) +auth.pass_key(hash, cert_name) if (not auth.username): # mismatch @@ -35,7 +35,7 @@ else: exit() # anticsrf+hash - if (auth.checkAnticSRF(anticsrf)): + if (auth.check_anticsrf(anticsrf)): res = auth.unlink(hash) if (res == auth.SUCCESS): print('30 index.gmi\r\n') diff --git a/public/cgi/index.gmi b/public/cgi/index.gmi index b5177bd..0d51405 100755 --- a/public/cgi/index.gmi +++ b/public/cgi/index.gmi @@ -10,16 +10,16 @@ hash = os.environ.get('TLS_CLIENT_HASH') if (not hash): print('60 Authentication is required\r\n') exit() -certName = os.environ.get('REMOTE_USER') +cert_name = os.environ.get('REMOTE_USER') print('20 text/gemini\r\n') from auth import auth auth = auth('data/data.db') -auth.passKey(hash, certName) +auth.pass_key(hash, cert_name) print('Your hash:', auth.hash) -print('Your common name:', certName) +print('Your common name:', cert_name) if (auth.username): print('Your username:', auth.username) print('=> account/index.gmi manage your account')