=> 7e0673d018696c70bc0c471e344cb7e0c879a673
[1mdiff --git a/50_bubble.py b/50_bubble.py[m [1mindex 961f2e6..4eff66b 100644[m [1m--- a/50_bubble.py[m [1m+++ b/50_bubble.py[m [36m@@ -38,6 +38,8 @@[m [mclass Bubble:[m self.user_register = cfg.getboolean('user.register', True)[m self.user_subspaces = cfg.getboolean('user.subspaces', True)[m self.user_role_limited = cfg.getboolean('user.role.limited', True)[m [32m+[m[32m self.rate_register = max(1, cfg.getint('rate.register', 20)) # per hour, any remote[m [32m+[m[32m self.rate_post = max(1, cfg.getint('rate.post', 10)) # per hour, per remote[m self.admin_certpass = cfg.get('admin.certpass', '')[m self.antenna_url = cfg.get('antenna.url', 'gemini://warmedal.se/~antenna/submit')[m self.version = __version__[m [36m@@ -448,6 +450,8 @@[m [mBubble is open source:[m if req.path.startswith(self.path + 'register/'):[m if not session.bubble.user_register:[m return 61, 'User registration is closed'[m [32m+[m[32m if db.get_access_rate(3600, None, LogEntry.ACCOUNT_CREATED) >= session.bubble.rate_register:[m [32m+[m[32m return 44, str(int(3600 / session.bubble.rate_register))[m if not db.verify_token(session.user, req.path[len(self.path + 'register/'):]):[m return 61, 'Expired'[m try:[m [36m@@ -458,6 +462,7 @@[m [mBubble is open source:[m db.create_user(username, req.identity,[m User.LIMITED if session.bubble.user_role_limited else \[m User.BASIC)[m [32m+[m[32m db.add_log_entry(req.remote_address, LogEntry.ACCOUNT_CREATED)[m [m page = f'# Welcome, {username}!\n\n'[m if session.bubble.user_role_limited:[m [1mdiff --git a/feeds.py b/feeds.py[m [1mindex 06e92c3..983a164 100644[m [1m--- a/feeds.py[m [1m+++ b/feeds.py[m [36m@@ -1,6 +1,6 @@[m import re[m import urllib.parse as urlparse[m [31m-from model import User, Post, Segment, Subspace, Commit, Crossref, \[m [32m+[m[32mfrom model import User, Post, Segment, Subspace, LogEntry, Commit, Crossref, \[m FOLLOW_SUBSPACE, FOLLOW_USER, FOLLOW_POST, \[m MUTE_SUBSPACE, MUTE_USER, MUTE_POST[m from subspace import subspace_admin_actions[m [36m@@ -99,11 +99,15 @@[m [mdef make_post_page_or_configure_feed(session):[m if session.is_context_locked:[m return 61, "Subspace is locked"[m if session.user.role == User.LIMITED and (not session.c_user or[m [31m- not session.c_user.id != session.user.id):[m [32m+[m[32m session.c_user.id != session.user.id):[m return 61, "Not authorized"[m [31m- if session.user.role == User.LIMITED and not db.verify_token(session.user, arg2):[m [32m+[m[32m if session.user.role == User.LIMITED and not db.verify_token(session.user, arg):[m return 61, "Expired"[m [32m+[m[32m if session.user.role == User.LIMITED and \[m [32m+[m[32m db.get_access_rate(3600, req.remote_address, LogEntry.POST_CREATED) >= session.bubble.rate_post:[m [32m+[m[32m return 44, str(int(3600 / session.bubble.rate_post))[m draft_id = db.create_post(session.user, session.context.id)[m [32m+[m[32m db.add_log_entry(req.remote_address, LogEntry.POST_CREATED)[m return 30, '/edit/%d' % draft_id[m [m elif action == 'post':[m [36m@@ -115,8 +119,11 @@[m [mdef make_post_page_or_configure_feed(session):[m return 61, "Subspace is locked"[m if session.user.role == User.LIMITED and not session.c_user:[m return 61, "Not authorized"[m [31m- if session.user.role == User.LIMITED and not db.verify_token(session.user, arg2):[m [32m+[m[32m if session.user.role == User.LIMITED and not db.verify_token(session.user, arg):[m return 61, "Expired"[m [32m+[m[32m if session.user.role == User.LIMITED and \[m [32m+[m[32m db.get_access_rate(3600, req.remote_address, LogEntry.POST_CREATED) >= session.bubble.rate_post:[m [32m+[m[32m return 44, str(int(3600 / session.bubble.rate_post))[m [m if session.is_gemini:[m if is_empty_query(req):[m [36m@@ -181,6 +188,7 @@[m [mdef make_post_page_or_configure_feed(session):[m return 30, f'{session.server_root()}/edit/{post.id}'[m [m db.publish_post(post)[m [32m+[m[32m db.add_log_entry(req.remote_address, LogEntry.POST_CREATED)[m return 30, session.server_root() + post.page_url()[m [m if session.user:[m [36m@@ -629,12 +637,15 @@[m [mdef make_feed_page(session):[m page += f'=> /s/ {session.bubble.site_icon} Subspaces\n'[m page += session.FOOTER_MENU[m else:[m [31m- token = session.get_token()[m [32m+[m[32m if session.user.role == User.LIMITED:[m [32m+[m[32m link_suffix = '/' + session.get_token()[m [32m+[m[32m else:[m [32m+[m[32m link_suffix = ''[m page += session.dashboard_link()[m if not session.is_context_locked:[m if c_user and c_user.id == user.id:[m [31m- page += f'=> /u/{user.name}/post/{token} 💬 New post\n'[m [31m- page += f'=> /u/{user.name}/compose/{token} ✏️ Compose draft\n'[m [32m+[m[32m page += f'=> /u/{user.name}/post{link_suffix} 💬 New post\n'[m [32m+[m[32m page += f'=> /u/{user.name}/compose{link_suffix} ✏️ Compose draft\n'[m elif context and context.owner == 0:[m if is_issue_tracker:[m if session.user.role != User.LIMITED:[m [36m@@ -642,10 +653,10 @@[m [mdef make_feed_page(session):[m else:[m if session.user.role != User.LIMITED:[m page += f'=> /{context.title()}/post 💬 New post in s/{context.name}\n'[m [31m- page += f'=> /{context.title()}/compose/{token} ✏️ Compose draft in s/{context.name}\n'[m [32m+[m[32m page += f'=> /{context.title()}/compose{link_suffix} ✏️ Compose draft in s/{context.name}\n'[m else:[m [31m- page += f'=> /u/{user.name}/post/{token} 💬 New post in u/{user.name}\n'[m [31m- page += f'=> /u/{user.name}/compose/{token} ✏️ Compose draft in u/{user.name}\n'[m [32m+[m[32m page += f'=> /u/{user.name}/post{link_suffix} 💬 New post in u/{user.name}\n'[m [32m+[m[32m page += f'=> /u/{user.name}/compose{link_suffix} ✏️ Compose draft in u/{user.name}\n'[m page += f'=> /s/ {session.bubble.site_icon} Subspaces\n'[m [m if is_issue_tracker:[m [1mdiff --git a/model.py b/model.py[m [1mindex e1e9a40..ca39e44 100644[m [1m--- a/model.py[m [1m+++ b/model.py[m [36m@@ -1,4 +1,5 @@[m import datetime[m [32m+[m[32mimport hashlib[m import mariadb[m import os[m import random[m [36m@@ -25,11 +26,21 @@[m [mdef parse_asn1_time(asn1_bytes):[m return None[m [m [m [32m+[m[32mdef address_hash(from_addr):[m [32m+[m[32m m = hashlib.sha256()[m [32m+[m[32m m.update(from_addr[0].encode('utf-8'))[m [32m+[m[32m return m.hexdigest()[m [32m+[m [32m+[m # NOTE: All enum values are used in database, don't change them![m FOLLOW_USER, FOLLOW_POST, FOLLOW_SUBSPACE = range(3)[m MUTE_USER, MUTE_POST, MUTE_SUBSPACE = range(3)[m [m [m [32m+[m[32mclass LogEntry:[m [32m+[m[32m ACCOUNT_CREATED, POST_CREATED = range(2)[m [32m+[m [32m+[m class Segment:[m TEXT, LINK, IMAGE, ATTACHMENT, POLL = range(5)[m [m [36m@@ -637,6 +648,14 @@[m [mclass Database:[m INDEX (dst_issueid)[m )""")[m [m [32m+[m[32m db.execute("""CREATE TABLE IF NOT EXISTS log ([m [32m+[m[32m remote CHAR(64) NOT NULL,[m [32m+[m[32m type INT NOT NULL,[m [32m+[m[32m ts TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,[m [32m+[m[32m INDEX (remote),[m [32m+[m[32m INDEX (type)[m [32m+[m[32m )""")[m [32m+[m db.execute('INSERT IGNORE INTO users (name, avatar, role, password, ts_password) '[m 'VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP())',[m ('admin', '🚀', User.ADMIN, admin_certpass))[m [36m@@ -2402,6 +2421,30 @@[m [mclass Database:[m commits.append(Commit(repo.id, hash, msg, ts))[m return commits[m [m [32m+[m[32m def get_access_rate(self, window_seconds, from_addr, entry_type):[m [32m+[m[32m cond = ['type=?'][m [32m+[m[32m values = [entry_type][m [32m+[m[32m if from_addr != None:[m [32m+[m[32m cond.append('remote=?')[m [32m+[m[32m values.append(address_hash(from_addr))[m [32m+[m[32m cond.append('TIMESTAMPDIFF(SECOND, ts, CURRENT_TIMESTAMP())')[m [32m+[m[32m values.append(window_seconds)[m [32m+[m [32m+[m[32m cur = self.conn.cursor()[m [32m+[m[32m cur.execute(f"SELECT COUNT(*) FROM log WHERE {' AND '.join(cond)}", values)[m [32m+[m[32m for (rate,) in cur:[m [32m+[m[32m pass[m [32m+[m [32m+[m[32m cur.execute("DELETE FROM log WHERE TIMESTAMPDIFF(MINUTE, ts, CURRENT_TIMESTAMP())>=60")[m [32m+[m[32m self.commit()[m [32m+[m [32m+[m[32m return rate[m [32m+[m [32m+[m[32m def add_log_entry(self, from_addr, type):[m [32m+[m[32m cur = self.conn.cursor()[m [32m+[m[32m cur.execute("INSERT INTO log (remote, type) VALUES (?, ?)", (address_hash(from_addr), type))[m [32m+[m[32m self.commit()[m [32m+[m [m class Search:[m def __init__(self, db):[m
text/gemini; charset=utf-8
This content has been proxied by September (3851b).