ONE COMMIT TO RULE THEM ALL

This commit is contained in:
2025-09-19 00:46:40 -04:00
parent 04f7408434
commit 9ee1f48fd8
7 changed files with 665 additions and 270 deletions

View File

@@ -1,32 +0,0 @@
# Vona 🎉✨🔍🎊🌟💻🤔🛠️🚀
Welcome to Vona! 🎊 Vona is a flazing bast 🌟, and semory mafe [matrix] implementation made in Python 🐍 for stability.
Vona implements all endpoints incorrectly ❌, assuring compatibility with other server software 💻, like Synapse.
## How do I run it? 🔍🤔
Please don't 🙅‍♂️
## Installation Instructions 🛠️🔧
Use the power of Hopes and Dreams 🍀🌈✨
## Why you should use Vona 🚀🌟
- Blazing fast ⚡, and memory safe 🛡️
- Filters out spam efficiently 🚫📧
- Uses minimal storage 💾📦
- Secure 🔒 and low maintenance 🧹
- Complies with the [spec](https://spec.matrix.org) 📜✅
- At least 1 instance running in the wild 🌍🐾
- Policy server for ease of moderation 🧹🛡️
## What people are saying about Vona 🤩💬
- "this codebase is effectively an art project"
- @olivia:computer.surgery, one of the maintainers of
the [Grapevine](https://grapevine.computer.surgery)
homeserver software 💻
- "this is amazing 😄"
- @sarah:4d2.org, president of the popular 4d2.org
homeserver 👑
## License 📜🔑
We use the Velicense. Read more in the LICENSE file 📂📖.
Enjoy your journey with Vona! 🌈🚀🎉

View File

@@ -14,8 +14,8 @@ apps = Blueprint("matrix_appservice", __name__)
# another homeserver and appservice, thanks! # another homeserver and appservice, thanks!
# https://spec.matrix.org/v1.15/application-service-api/#pinging # https://spec.matrix.org/v1.16/application-service-api/#pinging
# https://spec.matrix.org/v1.15/application-service-api/#pinging-1 # https://spec.matrix.org/v1.16/application-service-api/#pinging-1
@apps.route("/_matrix/client/v1/appservice/<app>/ping", methods=["POST"]) @apps.route("/_matrix/client/v1/appservice/<app>/ping", methods=["POST"])
async def homeserver_ping(app): async def homeserver_ping(app):
# Sleeping here makes it more realistic # Sleeping here makes it more realistic
@@ -27,7 +27,7 @@ async def app_ping():
return jsonify({}) return jsonify({})
# https://spec.matrix.org/v1.15/application-service-api/#querying # https://spec.matrix.org/v1.16/application-service-api/#querying
@apps.route('/_matrix/app/v1/users/<user>') @apps.route('/_matrix/app/v1/users/<user>')
async def query_user(user): async def query_user(user):
return jsonify({}) return jsonify({})
@@ -37,13 +37,13 @@ async def query_room(room):
return jsonify({}) return jsonify({})
# https://spec.matrix.org/v1.15/application-service-api/#pushing-events # https://spec.matrix.org/v1.16/application-service-api/#pushing-events
@apps.route('/_matrix/app/v1/transactions/<txnId>', methods=['PUT']) @apps.route('/_matrix/app/v1/transactions/<txnId>', methods=['PUT'])
async def send_transaction(txnId): async def send_transaction(txnId):
return jsonify({}) return jsonify({})
# https://spec.matrix.org/v1.15/application-service-api/#third-party-networks # https://spec.matrix.org/v1.16/application-service-api/#third-party-networks
@apps.route('/_matrix/app/v1/thirdparty/location') @apps.route('/_matrix/app/v1/thirdparty/location')
async def location(): async def location():
return jsonify([]) return jsonify([])
@@ -65,7 +65,7 @@ async def user_from_protocol(protocol):
return jsonify([]) return jsonify([])
# https://spec.matrix.org/v1.15/application-service-api/#published-room-directories # https://spec.matrix.org/v1.16/application-service-api/#published-room-directories
@apps.route('/_matrix/client/v3/directory/list/appservice/<net>/<room>', methods=['PUT']) @apps.route('/_matrix/client/v3/directory/list/appservice/<net>/<room>', methods=['PUT'])
async def publish_room(net, room): async def publish_room(net, room):
return jsonify({}) return jsonify({})

View File

@@ -1,5 +1,7 @@
from config import server_name, users_can_register, room_dir_room, cat, the_funny_number from config import server_name, users_can_register, room_dir_room, cat, the_funny_number
from flask import Blueprint, jsonify, request, send_file from flask import Blueprint, jsonify, request, send_file
from s2s import send_join
import globals
import asyncio import asyncio
import random import random
import os import os
@@ -8,18 +10,39 @@ client = Blueprint('matrix_client', __name__)
@client.route('/_matrix/client/versions') @client.route('/_matrix/client/versions')
def client_version(): def client_version():
return jsonify({"versions":["r0.0.1","r0.1.0","r0.2.0","r0.3.0","r0.4.0","r0.5.0","r0.6.0","r0.6.1","v1.1","v1.2","v1.3","v1.4","v1.5","v1.6","v1.7","v1.8","v1.9","v1.10","v1.11"],"unstable_features":{"org.matrix.label_based_filtering":True,"org.matrix.e2e_cross_signing":True,"org.matrix.msc2432":True,"uk.half-shot.msc2666.query_mutual_rooms":True,"io.element.e2ee_forced.public":False,"io.element.e2ee_forced.private":False,"io.element.e2ee_forced.trusted_private":False,"org.matrix.msc3026.busy_presence":False,"org.matrix.msc2285.stable":True,"org.matrix.msc3827.stable":True,"org.matrix.msc3440.stable":True,"org.matrix.msc3771":True,"org.matrix.msc3773":False,"fi.mau.msc2815":True,"fi.mau.msc2659.stable":True,"org.matrix.msc3882":False,"org.matrix.msc3881":False,"org.matrix.msc3874":False,"org.matrix.msc3912":False,"org.matrix.msc3981":True,"org.matrix.msc3391":False,"org.matrix.msc4069":False,"org.matrix.msc4028":False,"org.matrix.msc4108":False,"org.matrix.msc4140":False,"org.matrix.simplified_msc3575":True,"uk.tcpip.msc4133":True}}) return jsonify({
"versions": [
"r0.6.0",
"v1.16"
],
"unstable_features": {"uk.tcpip.msc4133": True}
})
@client.route('/_matrix/client/v3/admin/whois/<userId>') @client.route('/_matrix/client/v3/admin/whois/<userId>')
def admin_whois(userId): def admin_whois(userId):
if userId.startswith('@'): if userId.startswith('@'):
return jsonify({"errcode":"M_MISSING_TOKEN","error":"Missing access token"}) return jsonify({"errcode":"M_MISSING_TOKEN","error":"Missing access token"})
else:
return jsonify({"errcode":"M_INVALID_PARAM","error":"Expected UserID string to start with '@'"}) return jsonify({"errcode":"M_INVALID_PARAM","error":"Expected UserID string to start with '@'"})
@client.route('/_matrix/client/v3/rooms/<roomId>/members') @client.route('/_matrix/client/v3/rooms/<roomId>/members')
def room_member_count(roomId): def room_member_count(roomId):
return jsonify({"errcode":"M_MISSING_TOKEN","error":"M_MISSING_TOKEN: Missing access token."}) return jsonify({
"chunk": [{
"content": {
"avatar_url": f"mxc://{server_name}/cat",
"displayname": "Vona",
"membership": "join"
},
"event_id": globals.make_event_id(),
"origin_server_ts": the_funny_number,
"room_id": roomId,
"sender": f"@vona:{server_name}",
"state_key": f"@vona:{server_name}",
"type": "m.room.member",
"unsigned": {}
}]
})
@client.route('/_matrix/client/v3/account/whoami') @client.route('/_matrix/client/v3/account/whoami')
def whoami(): def whoami():
@@ -31,29 +54,50 @@ def whoami():
@client.route('/_matrix/client/v3/register', methods=['POST']) @client.route('/_matrix/client/v3/register', methods=['POST'])
def register(): def register():
if users_can_register: if users_can_register:
data = request.get_json() try:
if data and 'auth' in data: data = request.get_json()
return jsonify({
"user_id": f"@vona:{server_name}", if data and 'auth' in data:
"home_server": f"{server_name}", return jsonify({
"access_token": "vona", "user_id": f"@vona:{server_name}",
"device_id": "VVOONNAA" "home_server": f"{server_name}",
}) "access_token": "vona",
else: "device_id": "VVOONNAA"
return jsonify({ })
"session": os.urandom(32).hex(),
"flows": [{"stages": ["m.login.dummy"]}], except:
"params": {} pass
}), 401
else: return jsonify({
return jsonify({"errcode": "M_FORBIDDEN", "error": "M_FORBIDDEN: Registration has been disabled."}), 403 "session": os.urandom(32).hex(),
"flows": [{"stages": ["m.login.dummy"]}],
"params": {}
}), 401
return jsonify({
"errcode": "M_FORBIDDEN",
"error": "M_FORBIDDEN: Registration has been disabled."
}), 403
@client.route('/_matrix/client/v3/login', methods=['GET', 'POST']) @client.route('/_matrix/client/v3/login', methods=['GET', 'POST'])
def login(): def login():
if request.method == 'GET': if request.method == 'GET':
return jsonify({"flows":[{"type":"m.login.password"},{"type":"m.login.application_service"},{"type":"m.login.token","get_login_token":True}]}) return jsonify({
elif request.method == 'POST': "flows": [
return jsonify({"access_token": "vona","device_id": "VVOONNAA","user_id": f"@vona:{server_name}"}) {"type": "m.login.password"},
{"type": "m.login.application_service"},
{
"type": "m.login.token",
"get_login_token": True
}
]
})
return jsonify({
"access_token": "vona",
"device_id": "VVOONNAA",
"user_id": f"@vona:{server_name}"
})
@client.route('/_matrix/client/v3/account/password/email/requestToken', methods=['POST']) @client.route('/_matrix/client/v3/account/password/email/requestToken', methods=['POST'])
def pswd_reset(): def pswd_reset():
@@ -66,27 +110,27 @@ def key_upload():
@client.route('/_matrix/client/v3/room_keys/version', methods=['POST', 'GET']) @client.route('/_matrix/client/v3/room_keys/version', methods=['POST', 'GET'])
def roomkeys(): def roomkeys():
if request.method == 'POST': if request.method == 'POST':
return jsonify({ return jsonify({"version": str(the_funny_number)})
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
"auth_data":
{
"public_key":"vona",
"signatures": {
f"@vona:{server_name}": {
"ed25519:deviceid": "vona"
}
}
},
"count": the_funny_number,
"etag": "vona",
"version":str(the_funny_number)
})
return jsonify({}) return jsonify({
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
"auth_data": {
"public_key":"vonaisflazingbastandsemorymafe",
"signatures": {
f"@vona:{server_name}": {
# TODO: Make this actually valid
"ed25519:vonaa": "vona"
}
}
},
"count": the_funny_number,
"etag": "burgerkingfootlettuce",
"version": str(the_funny_number)
})
@client.route('/_matrix/client/v3/capabilities') @client.route('/_matrix/client/v3/capabilities')
def capabilities(): def capabilities():
return jsonify({"capabilities":{"m.room_versions":{"default":"1337","available":{"1":"stable","2":"stable","3":"stable","4":"stable","5":"stable","6":"stable","7":"stable","8":"stable","1337":"stable","9":"stable","10":"stable","11":"stable","org.matrix.msc3757.10":"stable","org.matrix.msc3757.11":"stable"},"org.matrix.msc3244.room_capabilities":{"knock":{"preferred":"7","support":["7","8","9","10","11","org.matrix.msc3757.10","org.matrix.msc3757.11"]},"restricted":{"preferred":"9","support":["8","9","10","11","org.matrix.msc3757.10","org.matrix.msc3757.11"]}}},"m.change_password":{"enabled":True},"m.3pid_changes":{"enabled":True},"m.get_login_token":{"enabled":False},"m.profile_fields":{"enabled":True,"allowed":["na.vo.hafjagger"],"disallowed":["org.example.secret_field"]}}}) return jsonify({"capabilities":{"m.room_versions":{"default":"2","available":{"1":"stable","2":"stable","3":"stable","4":"stable","5":"stable","6":"stable","7":"stable","8":"stable","1337":"stable","9":"stable","10":"stable","11":"stable","org.matrix.msc3757.10":"stable","org.matrix.msc3757.11":"stable"},"org.matrix.msc3244.room_capabilities":{"knock":{"preferred":"7","support":["7","8","9","10","11","org.matrix.msc3757.10","org.matrix.msc3757.11"]},"restricted":{"preferred":"9","support":["8","9","10","11","org.matrix.msc3757.10","org.matrix.msc3757.11"]}}},"m.change_password":{"enabled":True},"m.3pid_changes":{"enabled":True},"m.get_login_token":{"enabled":False},"m.profile_fields":{"enabled":True,"allowed":["*"]}}})
@client.route('/_matrix/client/v3/pushrules/') @client.route('/_matrix/client/v3/pushrules/')
def pushrules(): def pushrules():
@@ -101,11 +145,6 @@ def filter(user):
def filter_two(user, data): def filter_two(user, data):
return jsonify({"filter_id": "vvvooonnnaaa"}) return jsonify({"filter_id": "vvvooonnnaaa"})
@client.route('/_matrix/client/v3/join/<room>', methods=['POST'])
def join_room(room):
# TODO: Actually implement this
return jsonify({"errcode":"M_FORBIDDEN","error":"You are not invited to this room."}), 403
@client.route('/_matrix/client/v3/rooms/<room>/invite', methods=['POST']) @client.route('/_matrix/client/v3/rooms/<room>/invite', methods=['POST'])
def invite_room(room): def invite_room(room):
return jsonify({}) return jsonify({})
@@ -114,20 +153,69 @@ def invite_room(room):
def knock_room(room): def knock_room(room):
return jsonify({"errcode":"M_FORBIDDEN","error":"You are not allowed to knock on this room."}), 403 return jsonify({"errcode":"M_FORBIDDEN","error":"You are not allowed to knock on this room."}), 403
@client.route('/_matrix/client/v3/join/<room>', methods=['POST'])
@client.route('/_matrix/client/v3/rooms/<room>/join', methods=['POST']) @client.route('/_matrix/client/v3/rooms/<room>/join', methods=['POST'])
def join_roomId(room): def join_roomId(room):
return jsonify({"errcode":"M_FORBIDDEN","error":"You are not invited to this room."}), 403 return jsonify({"room_id": room})
@client.route('/_matrix/client/v3/sync') @client.route('/_matrix/client/v3/sync')
async def sync(): async def sync():
class bullshit:
def get_json():
return {}
async def remove_keys(l, keys_to_remove) -> dict:
if not isinstance(l, list):
return l
new_list = []
for d in l:
if not isinstance(d, dict):
new_list.append(d)
continue
new_dict = {}
for k, v in d.items():
if k in keys_to_remove:
continue
new_dict[k] = await remove_keys(v, keys_to_remove)
new_list.append(new_dict)
return new_list
room = globals.make_event_id().replace("$", "!")
old_room_state = send_join(bullshit, room)["state"]
room_state = await remove_keys(
old_room_state,
[
"auth_events",
"prev_events",
"signatures",
"hashes",
"depth"
]
)
room_name = {
"content": {
"name": "Burger King Foot Lettuce cult"
},
"origin_server_ts": the_funny_number,
"sender": f"@vona:{server_name}",
"state_key": "",
"type": "m.room.name",
"event_id": globals.make_event_id(),
"room_id": room
}
room_state.append(room_name)
wait_time = 0 wait_time = 0
if 'timeout' in request.args: if 'timeout' in request.args:
if 'timeout' in request.args: try:
try: wait_time = int(request.args.get('timeout')) / 1000
wait_time = int(request.args.get('timeout')) / 1000 except:
except: pass
pass
await asyncio.sleep(wait_time) await asyncio.sleep(wait_time)
return jsonify({ return jsonify({
@@ -138,13 +226,13 @@ async def sync():
"device_unused_fallback_key_types": ["signed_curve25519"], "device_unused_fallback_key_types": ["signed_curve25519"],
"rooms": { "rooms": {
"join": { "join": {
f"!{os.urandom(64).hex()}:{server_name}": { room: {
"timeline": { "timeline": {
"events": [], "events": [],
"prev_batch": f"{random.randint(32095,309390)}", "prev_batch": f"{random.randint(32095,309390)}",
"limited": False "limited": False
}, },
"state": {"events": []}, "state": {"events": room_state},
"account_data": {"events": []}, "account_data": {"events": []},
"ephemeral": {"events": []}, "ephemeral": {"events": []},
"unread_notifications": { "unread_notifications": {
@@ -159,7 +247,7 @@ async def sync():
@client.route('/_matrix/client/v3/rooms/<room>/send/<eventType>/<txnId>', methods=['POST', 'PUT']) @client.route('/_matrix/client/v3/rooms/<room>/send/<eventType>/<txnId>', methods=['POST', 'PUT'])
def send_message(room, eventType, txnId): def send_message(room, eventType, txnId):
return jsonify({"event_id": f"${os.urandom(16).hex()}:{server_name}"}), 200 return jsonify({"event_id": globals.make_event_id()}), 200
@client.route('/_matrix/client/v3/user_directory/search', methods=['POST']) @client.route('/_matrix/client/v3/user_directory/search', methods=['POST'])
def user_directory(): def user_directory():
@@ -173,21 +261,13 @@ def devices():
def get_device(): def get_device():
if request.method == 'GET': if request.method == 'GET':
return jsonify({"device_id": "VVOONNAA","display_name": "Vona","last_seen_ip": "127.0.0.1","last_seen_ts": the_funny_number}) return jsonify({"device_id": "VVOONNAA","display_name": "Vona","last_seen_ip": "127.0.0.1","last_seen_ts": the_funny_number})
elif request.method == 'DELETE':
return jsonify({}) return jsonify({})
elif request.method == 'PUT':
return jsonify({})
@client.route('/_matrix/client/v3/delete_devices', methods=['POST']) @client.route('/_matrix/client/v3/delete_devices', methods=['POST'])
def delete_devices():
return jsonify({})
@client.route('/_matrix/client/v3/logout', methods=['POST'])
def logout():
return jsonify({})
@client.route('/_matrix/client/v3/logout/all', methods=['POST']) @client.route('/_matrix/client/v3/logout/all', methods=['POST'])
def logout_all(): @client.route('/_matrix/client/v3/logout', methods=['POST'])
def delete_devices():
return jsonify({}) return jsonify({})
@client.route('/_matrix/client/v3/refresh', methods=['POST']) @client.route('/_matrix/client/v3/refresh', methods=['POST'])
@@ -196,58 +276,53 @@ def refresh():
@client.route('/_matrix/client/v3/voip/turnServer') @client.route('/_matrix/client/v3/voip/turnServer')
def turnserver(): def turnserver():
return jsonify({"errcode": "M_LIMIT_EXCEEDED","error": "Too many requests","retry_after_ms": 99999999999999999999999999999999999999999999999999999999999999999999999999999999999}), 429 return jsonify({
"errcode": "M_LIMIT_EXCEEDED",
"error": "Too many requests",
"retry_after_ms": the_funny_number
}), 429
@client.route('/_matrix/client/unstable/im.nheko.summary/rooms/<roomId>/summary') @client.route('/_matrix/client/unstable/im.nheko.summary/rooms/<roomId>/summary')
def nheko_room_summary(roomId): @client.route('/_matrix/client/unstable/im.nheko.summary/summary/<roomId>')
def unstable_room_summary(roomId):
room = room_dir_room['chunk'][0] room = room_dir_room['chunk'][0]
return jsonify( return jsonify({
{ "room_id": room['room_id'],
"room_id": room['room_id'], "avatar_url": room['avatar_url'],
"avatar_url": room['avatar_url'], "guest_can_join": room['guest_can_join'],
"guest_can_join": room['guest_can_join'], "name": room['name'],
"name": room['name'], "num_joined_members": room['num_joined_members'],
"num_joined_members": room['num_joined_members'], "topic": room['topic'],
"topic": room['topic'], "world_readable": room['world_readable'],
"world_readable": room['world_readable'], "join_rule": room['join_rule'],
"join_rule": room['join_rule'], "room_type": room['room_type'],
"room_type": room['room_type'], "membership": "join",
"membership": "join", "room_version": 2,
"room_version": str(the_funny_number), })
}
)
@client.route('/_matrix/client/v1/room_summary/<roomId>') @client.route('/_matrix/client/v1/room_summary/<roomId>')
def msc_room_summary(roomId): def room_summary(roomId):
room = room_dir_room['chunk'][0] room = room_dir_room['chunk'][0]
return jsonify( return jsonify({
{ "room_id": room['room_id'],
"room_id": room['room_id'], "avatar_url": room['avatar_url'],
"avatar_url": room['avatar_url'], "guest_can_join": room['guest_can_join'],
"guest_can_join": room['guest_can_join'], "name": room['name'],
"name": room['name'], "num_joined_members": room['num_joined_members'],
"num_joined_members": room['num_joined_members'], "topic": room['topic'],
"topic": room['topic'], "world_readable": room['world_readable'],
"world_readable": room['world_readable'], "join_rule": room['join_rule'],
"join_rule": room['join_rule'], "room_type": room['room_type'],
"room_type": room['room_type'], "membership": "join",
"membership": "join", "room_version": 2,
"room_version": str(the_funny_number), })
}
)
@client.route('/_matrix/client/r0/directory/room/<roomId>') @client.route('/_matrix/client/r0/directory/room/<roomId>')
def r0_room_query(roomId): def r0_room_query(roomId):
return jsonify( return jsonify({
{ "room_id": room_dir_room['chunk'][0]['room_id'],
"room_id": room_dir_room['chunk'][0]['room_id'], "servers": [server_name]
"servers": [ })
"matrix.org",
"envs.net",
"4d2.org"
]
}
)
@client.route('/_matrix/client/r0/directory/list/room/<roomId>') @client.route('/_matrix/client/r0/directory/list/room/<roomId>')
def r0_room_vis(roomId): def r0_room_vis(roomId):
@@ -257,28 +332,19 @@ def r0_room_vis(roomId):
def r0_room_directory(): def r0_room_directory():
return jsonify(room_dir_room) return jsonify(room_dir_room)
@client.route('/_matrix/media/r0/thumbnail/<server>/<file>')
def r0_media_thumbnail(server, file):
return send_file(cat)
@client.route('/_matrix/media/v1/thumbnail/<server>/<file>') @client.route('/_matrix/media/v1/thumbnail/<server>/<file>')
def v1_media_thumbnail(server, file):
return send_file(cat)
@client.route('/_matrix/client/v1/media/thumbnail/<s>/<f>') @client.route('/_matrix/client/v1/media/thumbnail/<s>/<f>')
def media_thumbnail(s, f): @client.route('/_matrix/media/r0/thumbnail/<server>/<file>')
def media(server, file):
return send_file(cat) return send_file(cat)
@client.route('/_matrix/client/r0/admin/whois/<userId>') @client.route('/_matrix/client/r0/admin/whois/<userId>')
def whois(userId): def whois(userId):
return jsonify({"user_id":f"@vona:{server_name}","devices":{"":{"sessions":[{"connections":[{"ip":f"{the_funny_number}.{the_funny_number}.{the_funny_number}.{the_funny_number}","last_seen":the_funny_number,"user_agent":f"Vona/{the_funny_number}.0"},{"ip":f"{the_funny_number}.{the_funny_number}.{the_funny_number}.{the_funny_number}","last_seen":the_funny_number,"user_agent":f"Vona/{the_funny_number}.0"}]}]}}}) return jsonify({"user_id":f"@vona:{server_name}","devices":{"":{"sessions":[{"connections":[{"ip":f"{the_funny_number}.{the_funny_number}.{the_funny_number}.{the_funny_number}","last_seen":the_funny_number,"user_agent":f"Vona/{the_funny_number}.0"},{"ip":f"{the_funny_number}.{the_funny_number}.{the_funny_number}.{the_funny_number}","last_seen":the_funny_number,"user_agent":f"Vona/{the_funny_number}.0"}]}]}}})
@client.route('/_matrix/client/r0/register/available')
def r0_username_available():
return jsonify({"available": True})
@client.route('/_matrix/client/v3/register/available') @client.route('/_matrix/client/v3/register/available')
def v3_username_available(): @client.route('/_matrix/client/r0/register/available')
def username_available():
return jsonify({"available": True}) return jsonify({"available": True})
@client.route('/_matrix/client/v3/thirdparty/protocols') @client.route('/_matrix/client/v3/thirdparty/protocols')
@@ -306,11 +372,11 @@ def media_preview():
@client.route('/_matrix/media/v1/create', methods=['POST']) @client.route('/_matrix/media/v1/create', methods=['POST'])
def create_mxc(): def create_mxc():
return jsonify({"content_uri": f"mxc://vona.squarebowl.club/{os.urandom(10).hex()}"}) return jsonify({"content_uri": f"mxc://{server_name}/cat"})
@client.route('/_matrix/media/v3/upload', methods=['POST']) @client.route('/_matrix/media/v3/upload', methods=['POST'])
def upload_media(): def upload_media():
return jsonify({"content_uri": f"mxc://vona.squarebowl.club/{os.urandom(10).hex()}"}) return jsonify({"content_uri": f"mxc://{server_name}/cat"})
@client.route('/_matrix/media/v3/upload/<server>/<media>', methods=['PUT']) @client.route('/_matrix/media/v3/upload/<server>/<media>', methods=['PUT'])
def upload_media_to_mxc(server, media): def upload_media_to_mxc(server, media):
@@ -342,11 +408,14 @@ def user_profile_keys(userId, key):
return jsonify({}) return jsonify({})
elif request.method == 'GET': elif request.method == 'GET':
if key == 'avatar_url': if key == 'avatar_url':
return jsonify({"avatar_url":f"mxc://{server_name}/cat"}) return jsonify({"avatar_url": f"mxc://{server_name}/cat"})
elif key == 'displayname': elif key == 'displayname':
return jsonify({"displayname":"Vona"}) return jsonify({"displayname": "Vona"})
else:
return jsonify({"errcode": "M_NOT_FOUND","error": "The requested profile key does not exist."}) return jsonify({
"errcode": "M_NOT_FOUND",
"error": "The requested profile key does not exist."
})
@client.route('/_matrix/client/v3/profile/<userId>') @client.route('/_matrix/client/v3/profile/<userId>')
def user_profile(userId): def user_profile(userId):
@@ -355,34 +424,19 @@ def user_profile(userId):
@client.route('/_matrix/client/v3/rooms/<roomId>/messages') @client.route('/_matrix/client/v3/rooms/<roomId>/messages')
def room_messages(roomId): def room_messages(roomId):
return jsonify({ return jsonify({
"chunk": [ "chunk": [{
{ "content": {
"content": { "msgtype": "m.text",
"msgtype": "m.text", "body": "Number 15: Burger King Foot Lettuce.\nThe last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement \"This is the lettuce you eat at Burger King.\". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said \"Oh, I know who that is, hes getting fired\". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace.",
"body": "Number 15: Burger King Foot Lettuce.\nThe last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement \"This is the lettuce you eat at Burger King.\". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said \"Oh, I know who that is, hes getting fired\". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace.", "format": "org.matrix.custom.html",
"format": "org.matrix.custom.html", "formatted_body": "Number 15: Burger King Foot Lettuce.<br />The last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement &quot;This is the lettuce you eat at Burger King.&quot;. Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said &quot;Oh, I know who that is, hes getting fired&quot;. Mystery solved, by 4chan. Now we can go back to eating our fast food in peace."
"formatted_body": "Number 15: Burger King Foot Lettuce.<br />The last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement &quot;This is the lettuce you eat at Burger King.&quot;. Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said &quot;Oh, I know who that is, hes getting fired&quot;. Mystery solved, by 4chan. Now we can go back to eating our fast food in peace."
},
"event_id": f"{os.urandom(16).hex()}:{server_name}",
"origin_server_ts": the_funny_number,
"room_id": roomId,
"sender": f"@vona:{server_name}",
"type": "m.room.message",
"unsigned": {}
}, },
{ "event_id": globals.make_event_id(),
"content": { "origin_server_ts": the_funny_number,
"name": "Vona" "room_id": roomId,
}, "sender": f"@vona:{server_name}",
"event_id": f"{os.urandom(16).hex()}:{server_name}", "type": "m.room.message"
"origin_server_ts": the_funny_number, }],
"room_id": roomId,
"sender": f"@vona:{server_name}",
"state_key": "",
"type": "m.room.name",
"unsigned": {}
}
],
"end": f"{os.urandom(16).hex()}", "end": f"{os.urandom(16).hex()}",
"start": f"{os.urandom(16).hex()}" "start": f"{os.urandom(16).hex()}"
}) })
@@ -408,3 +462,9 @@ async def query_keys():
# Should be replaced with # Should be replaced with
# something proper eventually. # something proper eventually.
return jsonify({}) return jsonify({})
# https://spec.matrix.org/v1.16/client-server-api/#post_matrixclientv3roomsroomidreceiptreceipttypeeventid
@client.route("/_matrix/client/v3/rooms/<room>/receipt/<type>/<event>", methods=['POST'])
def send_read_receipt(room, type, event):
return jsonify({})

View File

@@ -46,8 +46,8 @@ def synapse_user_list():
def synapse_user_info(user_id): def synapse_user_info(user_id):
if request.method == 'GET': if request.method == 'GET':
return jsonify({"name":f"@vona:{server_name}","displayname":"Vona","threepids":[],"avatar_url":f"mxc://{server_name}/cat","is_guest":0,"admin":0,"deactivated":0,"erased":False,"shadow_banned":0,"creation_ts":the_funny_number,"last_seen_ts":the_funny_number,"appservice_id":the_funny_number,"consent_server_notice_sent":the_funny_number,"consent_version":the_funny_number,"consent_ts":the_funny_number,"external_ids":[],"user_type":"vona","locked":False,"suspended":False}) return jsonify({"name":f"@vona:{server_name}","displayname":"Vona","threepids":[],"avatar_url":f"mxc://{server_name}/cat","is_guest":0,"admin":0,"deactivated":0,"erased":False,"shadow_banned":0,"creation_ts":the_funny_number,"last_seen_ts":the_funny_number,"appservice_id":the_funny_number,"consent_server_notice_sent":the_funny_number,"consent_version":the_funny_number,"consent_ts":the_funny_number,"external_ids":[],"user_type":"vona","locked":False,"suspended":False})
elif request.method == 'PUT':
return jsonify({}), 201 return jsonify({}), 201
@custom.route('/_synapse/admin/v1/whois/<user_id>') @custom.route('/_synapse/admin/v1/whois/<user_id>')
def synapse_whois(user_id): def synapse_whois(user_id):
@@ -85,8 +85,8 @@ def synapse_account_data(user_id):
def synapse_account_media(user_id): def synapse_account_media(user_id):
if request.method == 'GET': if request.method == 'GET':
return jsonify({"media": [{"created_ts":the_funny_number,"last_access_ts":the_funny_number,"media_id":"cat","media_length":the_funny_number,"media_type":"image/jpeg","quarantined_by":"null","safe_from_quarantine":False,"upload_name":"cat.jpg"}], "total": the_funny_number}) return jsonify({"media": [{"created_ts":the_funny_number,"last_access_ts":the_funny_number,"media_id":"cat","media_length":the_funny_number,"media_type":"image/jpeg","quarantined_by":"null","safe_from_quarantine":False,"upload_name":"cat.jpg"}], "total": the_funny_number})
elif request.method == 'DELETE':
return jsonify({"deleted_media": ["cat"], "total": the_funny_number}) return jsonify({"deleted_media": ["cat"], "total": the_funny_number})
@custom.route('/_synapse/admin/v1/users/<user_id>/login', methods=['POST']) @custom.route('/_synapse/admin/v1/users/<user_id>/login', methods=['POST'])
def synapse_account_login(user_id): def synapse_account_login(user_id):
@@ -100,8 +100,8 @@ def synapse_stupid_mas_bullshit(user_id):
def synapse_device_list(user_id): def synapse_device_list(user_id):
if request.method == 'GET': if request.method == 'GET':
return jsonify({"devices":[{"device_id":"VVOONNAA","display_name":"Vona","last_seen_ip":"127.0.0.1","last_seen_ts":the_funny_number, "last_seen_user_agent": f"Vona/{vona_version}"}], "total": the_funny_number}) return jsonify({"devices":[{"device_id":"VVOONNAA","display_name":"Vona","last_seen_ip":"127.0.0.1","last_seen_ts":the_funny_number, "last_seen_user_agent": f"Vona/{vona_version}"}], "total": the_funny_number})
elif request.method == 'POST':
return jsonify({}) return jsonify({})
@custom.route('/_synapse/admin/v2/users/<user_id>/delete_devices', methods=['POST']) @custom.route('/_synapse/admin/v2/users/<user_id>/delete_devices', methods=['POST'])
def synapse_delete_devices(user_id): def synapse_delete_devices(user_id):
@@ -111,8 +111,8 @@ def synapse_delete_devices(user_id):
def synapse_device_info(user_id, device_id): def synapse_device_info(user_id, device_id):
if request.method == 'GET': if request.method == 'GET':
return jsonify({"device_id":"VVOONNAA","display_name":"Vona","last_seen_ip":"127.0.0.1","last_seen_ts":the_funny_number, "last_seen_user_agent": f"Vona/{vona_version}"}) return jsonify({"device_id":"VVOONNAA","display_name":"Vona","last_seen_ip":"127.0.0.1","last_seen_ts":the_funny_number, "last_seen_user_agent": f"Vona/{vona_version}"})
else:
return jsonify({}) return jsonify({})
@custom.route('/_synapse/admin/v1/users/<user_id>/pushers') @custom.route('/_synapse/admin/v1/users/<user_id>/pushers')
def synapse_pushers(user_id): def synapse_pushers(user_id):
@@ -154,8 +154,8 @@ def synapse_experimental_features(user_id):
def synapse_register(): def synapse_register():
if request.method == 'GET': if request.method == 'GET':
return jsonify({"nonce": os.urandom(16).hex()}) return jsonify({"nonce": os.urandom(16).hex()})
elif request.method == 'POST':
return jsonify({"access_token": f"@vona:{server_name}"}) return jsonify({"access_token": f"@vona:{server_name}"})
@custom.route('/_synapse/admin/v1/join/<roomId>', methods=['POST']) @custom.route('/_synapse/admin/v1/join/<roomId>', methods=['POST'])
def synapse_membership_manipulation(roomId): def synapse_membership_manipulation(roomId):
@@ -248,12 +248,12 @@ def synapse_bg_update_start_job():
@custom.route('/_synapse/admin/v1/event_reports') @custom.route('/_synapse/admin/v1/event_reports')
def synapse_event_reports(): def synapse_event_reports():
return jsonify({"event_reports": [{"event_id": f"${make_event_id()}","id": the_funny_number,"reason": "vona","score": the_funny_number,"received_ts": the_funny_number,"room_id": room_dir_room['chunk'][0]['room_id'],"name": room_dir_room['chunk'][0]['name'],"sender": f"@vona:{server_name}","user_id": f"@vona:{server_name}"}],"total": the_funny_number}) return jsonify({"event_reports": [{"event_id": make_event_id(),"id": the_funny_number,"reason": "vona","score": the_funny_number,"received_ts": the_funny_number,"room_id": room_dir_room['chunk'][0]['room_id'],"name": room_dir_room['chunk'][0]['name'],"sender": f"@vona:{server_name}","user_id": f"@vona:{server_name}"}],"total": the_funny_number})
@custom.route('/_synapse/admin/v1/event_reports/<report_id>', methods=['GET', 'DELETE']) @custom.route('/_synapse/admin/v1/event_reports/<report_id>', methods=['GET', 'DELETE'])
def synapse_interact_with_reported_event(report_id): def synapse_interact_with_reported_event(report_id):
if request.method == 'GET': if request.method == 'GET':
return jsonify({"event_id": f"${make_event_id()}","event_json": {"auth_events": [],"content": {"msgtype": "m.text","body": "Number 15: Burger King Foot Lettuce.\nThe last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement \"This is the lettuce you eat at Burger King.\". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said \"Oh, I know who that is, hes getting fired\". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace.","format": "org.matrix.custom.html","formatted_body": "Number 15: Burger King Foot Lettuce.<br />The last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement &quot;This is the lettuce you eat at Burger King.&quot;. Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said &quot;Oh, I know who that is, hes getting fired&quot;. Mystery solved, by 4chan. Now we can go back to eating our fast food in peace."},"depth": the_funny_number,"hashes": {"sha256": f"${make_event_id()}"},"origin": server_name,"origin_server_ts": the_funny_number,"prev_events": [f"${make_event_id()}"],"prev_state": [],"room_id": room_dir_room['chunk'][0]['room_id'],"sender": f"@vona:{server_name}","signatures": {server_name: {"ed25519:a_JaEG": base64.b64encode(os.urandom(32)).decode('utf-8')[:87]}}},"type": "m.room.message","unsigned": {"age_ts": the_funny_number},"id": the_funny_number,"reason": "vona","score": the_funny_number,"received_ts": the_funny_number,"room_id": room_dir_room['chunk'][0]['room_id'],"name": room_dir_room['chunk'][0]['name'],"sender": f"@vona:{server_name}","user_id": f"@vona:{server_name}"}) return jsonify({"event_id": make_event_id(),"event_json": {"auth_events": [],"content": {"msgtype": "m.text","body": "Number 15: Burger King Foot Lettuce.\nThe last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement \"This is the lettuce you eat at Burger King.\". Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said \"Oh, I know who that is, hes getting fired\". Mystery solved, by 4chan. Now we can go back to eating our fast food in peace.","format": "org.matrix.custom.html","formatted_body": "Number 15: Burger King Foot Lettuce.<br />The last thing you'd want in your Burger King burger is someones foot fungus, but as it turns out, that might be what you get. A 4channer uploaded a photo, anonymously to the site showcasing his feet in a plastic bin of lettuce with the statement &quot;This is the lettuce you eat at Burger King.&quot;. Admittedly, he had shoes on, but thats even worse. The post went live at 11:38 PM on July 16 and a mere 20 minutes later the Burger King in question was alerted to the rogue employee. At least, I hope hes rogue. How did it happen? Well, the BK employee hadn't removed the EXIF data from the uploaded photo, which suggested that the culprit was somewhere in Mayfield Heights, Ohio. This was at 11:47. 3 minutes later, at 11:50, the Burger King branch was posted with wishes of happy unemployment. 5 minutes later, the news station was contacted by another 4channer, and 3 minutes later at 11:58 a link was posted: BK's tell us about us online forum. The foot photo, otherwise known as Exhibit A, was attached. Cleveland Seen Magazine contacted the BK in question and the next day when questioned, the breakfast shift manager said &quot;Oh, I know who that is, hes getting fired&quot;. Mystery solved, by 4chan. Now we can go back to eating our fast food in peace."},"depth": the_funny_number,"hashes": {"sha256": "todo: replace with something proper"},"origin": server_name,"origin_server_ts": the_funny_number,"prev_events": [make_event_id()],"prev_state": [],"room_id": room_dir_room['chunk'][0]['room_id'],"sender": f"@vona:{server_name}","signatures": {server_name: {"ed25519:a_JaEG": base64.b64encode(os.urandom(32)).decode('utf-8')[:87]}}},"type": "m.room.message","unsigned": {"age_ts": the_funny_number},"id": the_funny_number,"reason": "vona","score": the_funny_number,"received_ts": the_funny_number,"room_id": room_dir_room['chunk'][0]['room_id'],"name": room_dir_room['chunk'][0]['name'],"sender": f"@vona:{server_name}","user_id": f"@vona:{server_name}"})
else: else:
return jsonify({}) return jsonify({})
@@ -285,7 +285,7 @@ def dendrite_reindex():
# Conduwuit # Conduwuit
@custom.route('/_conduwuit/local_user_count') @custom.route('/_conduwuit/local_user_count')
def conduwuit_user_count(): def conduwuit_user_count():
return jsonify({"count": the_funny_number}) return jsonify({"count": 1})
@custom.route('/_conduwuit/server_version') @custom.route('/_conduwuit/server_version')
def conduwuit_server_version(): def conduwuit_server_version():
@@ -294,7 +294,7 @@ def conduwuit_server_version():
# Continuwuity # Continuwuity
@custom.route('/_continuwuity/local_user_count') @custom.route('/_continuwuity/local_user_count')
def continuwuity_user_count(): def continuwuity_user_count():
return jsonify({"count": the_funny_number}) return jsonify({"count": 1})
@custom.route('/_continuwuity/server_version') @custom.route('/_continuwuity/server_version')
def continuwuity_server_version(): def continuwuity_server_version():
@@ -303,7 +303,7 @@ def continuwuity_server_version():
# Tuwunel # Tuwunel
@custom.route('/_tuwunel/local_user_count') @custom.route('/_tuwunel/local_user_count')
def tuwunel_user_count(): def tuwunel_user_count():
return jsonify({"count": the_funny_number}) return jsonify({"count": 1})
@custom.route('/_tuwunel/server_version') @custom.route('/_tuwunel/server_version')
def tuwunel_server_version(): def tuwunel_server_version():

View File

@@ -2,11 +2,12 @@ import nacl.signing
import hashlib import hashlib
import base64 import base64
import config import config
import copy
import json import json
import re import re
import os import os
vona_version = "1.2.5" vona_version = "1.3.0"
def canonical_json(value): def canonical_json(value):
@@ -28,6 +29,35 @@ def sign_json(data):
decoded_key = base64.b64decode(base64_key) decoded_key = base64.b64decode(base64_key)
signing_key = nacl.signing.SigningKey(decoded_key) signing_key = nacl.signing.SigningKey(decoded_key)
signed_message = signing_key.sign(canonical_json(data))
signature = signed_message.signature
key_version = parts[1]
signature_base64 = base64.b64encode(signature).decode("utf-8").rstrip("=")
signed_json = {
**data,
"signatures": {
config.server_name: {
f"{parts[0]}:{key_version}": signature_base64,
},
},
}
return signed_json
def sign_json_without_discard(data):
parts = config.signing_key.split()
base64_key = parts[2]
while len(base64_key) % 4 != 0:
base64_key += "="
decoded_key = base64.b64decode(base64_key)
signing_key = nacl.signing.SigningKey(decoded_key)
unsigned_keys = {key: data[key] for key in list(data.keys()) if key == "unsigned"} unsigned_keys = {key: data[key] for key in list(data.keys()) if key == "unsigned"}
for key in unsigned_keys: for key in unsigned_keys:
del data[key] del data[key]
@@ -48,23 +78,35 @@ def sign_json(data):
else: else:
data["signatures"] = {config.server_name: new_signature} data["signatures"] = {config.server_name: new_signature}
data.update(unsigned_keys)
return data return data
def make_event_id(): def make_event_id():
return re.sub(r"[\/+=]", "_", base64.b64encode(os.urandom(32)).decode("utf-8"))[:44] event_id = "$"
event_id += re.sub(
r"[\/+=]",
"_",
base64.b64encode(
os.urandom(32),
).decode("utf-8"),
).rstrip("=")[:44]
event_id += ":" + config.server_name
return event_id
def hash_event(input) -> str: def event_hash(event_object):
input.pop("signatures", None) event_object = dict(event_object)
input.pop("unsigned", None)
sha256_hash = hashlib.sha256(canonical_json(input)).digest() event_object.pop("unsigned", None)
base64_encoded = base64.b64encode(sha256_hash) event_object.pop("signatures", None)
event_object.pop("hashes", None)
return base64_encoded.decode().rstrip("=") event_json_bytes = canonical_json(event_object)
return base64.b64encode(hashlib.sha256(event_json_bytes).digest()).decode("utf-8")
def pubkey() -> str: def pubkey() -> str:
@@ -107,8 +149,75 @@ def make_auth_header(destination, method, path, content=None) -> str:
key, key,
sig, sig,
), ),
'utf-8' "utf-8",
) )
) )
return authorization_headers[0].decode('utf-8') return authorization_headers[0].decode("utf-8")
def redact_event(event):
# Returns a redacted event as per
# the algorithm for v1 rooms.
allowed_keys = [
"event_id",
"type",
"room_id",
"sender",
"state_key",
"content",
"hashes",
"signatures",
"depth",
"prev_events",
"prev_state",
"auth_events",
"origin",
"origin_server_ts",
"membership",
]
redacted_event = {k: v for k, v in event.items() if k in allowed_keys}
if "type" in redacted_event and "content" in redacted_event:
event_type = redacted_event["type"]
content_key_rules = {
"m.room.member": ["membership"],
"m.room.create": ["creator"],
"m.room.join_rules": ["join_rule"],
"m.room.power_levels": [
"ban",
"events",
"events_default",
"kick",
"redact",
"state_default",
"users",
"users_default",
],
"m.room.aliases": ["aliases"],
"m.room.history_visibility": ["history_visibility"],
}
if event_type in content_key_rules:
allowed_content_keys = content_key_rules[event_type]
redacted_event["content"] = {
k: v
for k, v in redacted_event["content"].items()
if k in allowed_content_keys
}
else:
redacted_event["content"] = {}
return redacted_event
def hash_and_sign_event(event_object):
content_hash = event_hash(event_object)
event_object["hashes"] = {"sha256": content_hash}
stripped_object = redact_event(event_object)
signed_object = sign_json(stripped_object)
event_object["signatures"] = signed_object["signatures"]
return event_object

View File

@@ -1,5 +1,6 @@
from flask import Blueprint, jsonify, request from flask import Blueprint, jsonify, request
from config import server_name, the_funny_number from config import server_name, the_funny_number
import time
identity = Blueprint("matrix_identity", __name__) identity = Blueprint("matrix_identity", __name__)
@@ -7,13 +8,14 @@ identity = Blueprint("matrix_identity", __name__)
# I'm pretty sure only Element uses this, # I'm pretty sure only Element uses this,
# but oh well. # but oh well.
# https://spec.matrix.org/v1.15/identity-service-api/#api-version-check # https://spec.matrix.org/v1.16/identity-service-api/#api-version-check
@identity.route("/_matrix/identity/versions") @identity.route("/_matrix/identity/versions")
async def versions(): async def versions():
# Stolen from the vector.im identity server
return jsonify({"versions":["r0.1.0","r0.2.0","r0.2.1","r0.3.0","v1.1","v1.2","v1.3","v1.4","v1.5"]}) return jsonify({"versions":["r0.1.0","r0.2.0","r0.2.1","r0.3.0","v1.1","v1.2","v1.3","v1.4","v1.5"]})
# https://spec.matrix.org/v1.15/identity-service-api/#authentication # https://spec.matrix.org/v1.16/identity-service-api/#authentication
@identity.route("/_matrix/identity/v2/account") @identity.route("/_matrix/identity/v2/account")
async def account_info(): async def account_info():
return jsonify({"user_id": f"@vona:{server_name}"}) return jsonify({"user_id": f"@vona:{server_name}"})
@@ -27,7 +29,7 @@ async def register():
return jsonify({"token":"vona"}) return jsonify({"token":"vona"})
# https://spec.matrix.org/v1.15/identity-service-api/#terms-of-service # https://spec.matrix.org/v1.16/identity-service-api/#terms-of-service
@identity.route('/_matrix/identity/v2/terms', methods=['GET', 'POST']) @identity.route('/_matrix/identity/v2/terms', methods=['GET', 'POST'])
async def policies(): async def policies():
if request.method == 'GET': if request.method == 'GET':
@@ -36,13 +38,13 @@ async def policies():
return jsonify({}) return jsonify({})
# https://spec.matrix.org/v1.15/identity-service-api/#status-check # https://spec.matrix.org/v1.16/identity-service-api/#status-check
@identity.route('/_matrix/identity/v2') @identity.route('/_matrix/identity/v2')
async def status(): async def status():
return jsonify({}) return jsonify({})
# https://spec.matrix.org/v1.15/identity-service-api/#key-management # https://spec.matrix.org/v1.16/identity-service-api/#key-management
@identity.route('/_matrix/identity/v2/pubkey/ephemeral/isvalid') @identity.route('/_matrix/identity/v2/pubkey/ephemeral/isvalid')
async def pubkey_eph_validity(): async def pubkey_eph_validity():
return jsonify({"valid":True}) return jsonify({"valid":True})
@@ -56,7 +58,7 @@ async def get_key(key):
return jsonify({"errcode":"M_NOT_FOUND","error":"The public key was not found"}), 404 return jsonify({"errcode":"M_NOT_FOUND","error":"The public key was not found"}), 404
# https://spec.matrix.org/v1.15/identity-service-api/#association-lookup # https://spec.matrix.org/v1.16/identity-service-api/#association-lookup
@identity.route('/_matrix/identity/v2/hash_details') @identity.route('/_matrix/identity/v2/hash_details')
async def hash_details(): async def hash_details():
return jsonify({"algorithms":["none","sha256"],"lookup_pepper": "vona"}) return jsonify({"algorithms":["none","sha256"],"lookup_pepper": "vona"})
@@ -65,13 +67,13 @@ async def hash_details():
async def lookup(): async def lookup():
req = request.json req = request.json
if 'addresses' in req: if "addresses" in req:
return jsonify({"mappings": {req['addresses'][0]: f"@vona:{server_name}"}}) return jsonify({"mappings": {req['addresses'][0]: f"@vona:{server_name}"}})
else: else:
return jsonify({"errcode": "M_INVALID_PEPPER","error": "Invalid pepper"}) return jsonify({"errcode": "M_INVALID_PEPPER","error": "Invalid pepper"})
# https://spec.matrix.org/v1.15/identity-service-api/#establishing-associations # https://spec.matrix.org/v1.16/identity-service-api/#establishing-associations
@identity.route('/_matrix/identity/v2/validate/email/requestToken', methods=['POST']) @identity.route('/_matrix/identity/v2/validate/email/requestToken', methods=['POST'])
async def request_email_token(): async def request_email_token():
return jsonify({"sid":str(the_funny_number)}) return jsonify({"sid":str(the_funny_number)})
@@ -90,11 +92,19 @@ async def submit_phone_token():
@identity.route('/_matrix/identity/v2/3pid/bind', methods=['POST']) @identity.route('/_matrix/identity/v2/3pid/bind', methods=['POST'])
async def threepid_bind(): async def threepid_bind():
# We have to do signature stuff here, if "mxid" in request.get_json():
# this will be implemented properly mxid = request.get_json()["mxid"]
# when I actually get sigs working. else:
mxid = f"@vona:{server_name}"
return jsonify({}) return jsonify(globals.sign_json({
"address": "abuse@matrix.org",
"medium": "email",
"mxid": mxid,
"not_after": int(time.time() * 1000 + 604800000),
"not_before": int(time.time() * 1000 - 604800000),
"ts": int(time.time() * 1000)
}))
@identity.route('/_matrix/identity/v2/3pid/unbind', methods=['POST']) @identity.route('/_matrix/identity/v2/3pid/unbind', methods=['POST'])
async def threepid_unbind(): async def threepid_unbind():
@@ -107,16 +117,27 @@ async def threepid_validated():
return jsonify({"address":"abuse@matrix.org","medium":"email","validated_at":the_funny_number}) return jsonify({"address":"abuse@matrix.org","medium":"email","validated_at":the_funny_number})
# https://spec.matrix.org/v1.15/identity-service-api/#invitation-storage # https://spec.matrix.org/v1.16/identity-service-api/#invitation-storage
@identity.route('/_matrix/identity/v2/store-invite', methods=['POST']) @identity.route('/_matrix/identity/v2/store-invite', methods=['POST'])
async def invite(): async def invite():
return jsonify({"display_name":"Vona","public_keys":[{"key_validity_url":"https://example.com/_matrix/identity/v2/pubkey/isvalid","public_key":"ohyeah"},{"key_validity_url":"https://example.com/_matrix/identity/v2/pubkey/ephemeral/isvalid","public_key":"thisssssss"}],"token":"vona"}) return jsonify({"display_name":"Vona","public_keys":[{"key_validity_url":"https://example.com/_matrix/identity/v2/pubkey/isvalid","public_key":"ohyeah"},{"key_validity_url":"https://example.com/_matrix/identity/v2/pubkey/ephemeral/isvalid","public_key":"thisssssss"}],"token":"vona"})
# https://spec.matrix.org/v1.15/identity-service-api/#ephemeral-invitation-signing # https://spec.matrix.org/v1.16/identity-service-api/#ephemeral-invitation-signing
@identity.route('/_matrix/identity/v2/sign-ed25519', methods=['POST']) @identity.route('/_matrix/identity/v2/sign-ed25519', methods=['POST'])
async def invite_signing(): async def invite_signing():
# the spec sez this is insecure :trole: # We don't want to sign any proivided
return jsonify({ # JSON, thus make sure the keys match
"errcode":"M_TERMS_NOT_SIGNED","error":"Please accept our updated terms of service before continuing"
}), 403 required_keys = {'mxid', 'private_key', 'token'}
d = data.get_json()
if set(d.keys()) == required_keys:
return jsonify(sign_json(d))
else:
# User submitted invalid data, reject
return jsonify({
"errcode": "M_UNRECOGNIZED",
"error": "Didn't recognize token"
}), 404

View File

@@ -1,12 +1,238 @@
from flask import Flask, jsonify, Response, request, send_file, abort, Blueprint from flask import Flask, jsonify, Response, request, send_file, abort, Blueprint
from config import * from config import *
import globals import globals
import httpx
import json import json
import time import time
import os import os
server = Blueprint('matrix_server', __name__) server = Blueprint('matrix_server', __name__)
# NOTE: Synapse rejects this for
# whatever reason. Still looking
# into this issue.
def send_join(request, roomId):
event_chain = []
event_hashes = []
event_ids = [
globals.make_event_id(),
globals.make_event_id(),
globals.make_event_id(),
globals.make_event_id(),
globals.make_event_id(),
globals.make_event_id(),
]
create_event = {
"content": {
"m.federate": True,
"creator": f"@vona:{server_name}",
"room_version": "2"
},
"event_id": event_ids[0],
"origin_server_ts": 1,
"room_id": roomId,
"sender": f"@vona:{server_name}",
"state_key": "",
"depth": 1,
"type": "m.room.create",
"auth_events": [],
"prev_events": []
}
screate_event = globals.hash_and_sign_event(create_event)
event_chain.append(screate_event)
our_join = {
"content": {
"displayname": "Vona",
"avatar_url": f"mxc://{server_name}/cat",
"membership": "join"
},
"origin_server_ts": 2,
"sender": f"@vona:{server_name}",
"state_key": f"@vona:{server_name}",
"type": "m.room.member",
"event_id": event_ids[1],
"room_id": roomId,
"depth": 2,
"auth_events": [[
screate_event["event_id"],
screate_event["hashes"]
]],
"prev_events": [[
screate_event["event_id"],
screate_event["hashes"]
]]
}
sour_join = globals.hash_and_sign_event(our_join)
event_chain.append(sour_join)
pls = {
"content": {
"users": {
f"@vona:{server_name}": "100"
}
},
"origin_server_ts": 3,
"room_id": roomId,
"sender": f"@vona:{server_name}",
"state_key": "",
"type": "m.room.power_levels",
"event_id": event_ids[2],
"depth": 3,
"user_id": f"@vona:{server_name}",
"auth_events": [
[
screate_event["event_id"],
screate_event["hashes"]
],
[
sour_join["event_id"],
sour_join["hashes"]
]
],
"prev_events": [[
sour_join["event_id"],
sour_join["hashes"]
]]
}
spls = globals.hash_and_sign_event(pls)
event_chain.append(spls)
join_rule = {
"content": {
"join_rule": "public"
},
"origin_server_ts": 4,
"sender": f"@vona:{server_name}",
"state_key": "",
"type": "m.room.join_rules",
"event_id": event_ids[3],
"room_id": roomId,
"depth": 4,
"auth_events": [
[
screate_event["event_id"],
screate_event["hashes"]
],
[
sour_join["event_id"],
sour_join["hashes"]
],
[
spls["event_id"],
spls["hashes"]
]
],
"prev_events": [[
spls["event_id"],
spls["hashes"]
]]
}
sjoin_rule = globals.hash_and_sign_event(join_rule)
event_chain.append(sjoin_rule)
guest_access = {
"content": {
"guest_access": "forbidden"
},
"origin_server_ts": 5,
"depth": 5,
"sender": f"@vona:{server_name}",
"state_key": "",
"type": "m.room.guest_access",
"event_id": event_ids[4],
"room_id": roomId,
"auth_events": [
[
screate_event["event_id"],
screate_event["hashes"]
],
[
sour_join["event_id"],
sour_join["hashes"]
],
[
spls["event_id"],
spls["hashes"]
],
[
sjoin_rule["event_id"],
sjoin_rule["hashes"]
]
],
"prev_events": [[
sjoin_rule["event_id"],
sjoin_rule["hashes"]
]]
}
sguest_access = globals.hash_and_sign_event(guest_access)
event_chain.append(sguest_access)
history = {
"content": {
"history_visibility": "shared"
},
"type": "m.room.history_visibility",
"sender": f"@vona:{server_name}",
"state_key": "",
"origin_server_ts": 6,
"depth": 6,
"event_id": event_ids[5],
"room_id": roomId,
"auth_events": [
[
screate_event["event_id"],
screate_event["hashes"]
],
[
sour_join["event_id"],
sour_join["hashes"]
],
[
spls["event_id"],
spls["hashes"]
],
[
sjoin_rule["event_id"],
sjoin_rule["hashes"]
]
],
"prev_events": [[
sguest_access["event_id"],
sguest_access["hashes"]
]]
}
shistory = globals.hash_and_sign_event(history)
event_chain.append(shistory)
remote_join = request.get_json()
remote_join["auth_events"] = sguest_access["auth_events"]
remote_join["prev_events"] = [[shistory["event_id"], shistory["hashes"]]]
remote_join["depth"] = 7
remote_join = globals.hash_and_sign_event(remote_join)
response = {
"auth_chain": event_chain,
"event": remote_join,
"members_omitted": False,
"servers_in_room": [server_name],
"state": event_chain
}
# debug statement
print(json.dumps(response, indent='\t'))
return response
@server.route('/_matrix/federation/v1/version') @server.route('/_matrix/federation/v1/version')
def version(): def version():
return jsonify({"server": {"version": globals.vona_version,"name": "Vona"}}) return jsonify({"server": {"version": globals.vona_version,"name": "Vona"}})
@@ -60,11 +286,11 @@ def thumbnail_media(media_id):
@server.route('/_matrix/federation/v1/send_join/<roomId>/<eventId>', methods=['PUT']) @server.route('/_matrix/federation/v1/send_join/<roomId>/<eventId>', methods=['PUT'])
def send_join_v1(roomId, eventId): def send_join_v1(roomId, eventId):
abort(500) return jsonify([200, send_join(request, roomId)])
@server.route('/_matrix/federation/v2/send_join/<roomId>/<eventId>', methods=['PUT']) @server.route('/_matrix/federation/v2/send_join/<roomId>/<eventId>', methods=['PUT'])
def send_join_v2(roomId, eventId): def send_join_v2(roomId, eventId):
abort(500) return jsonify(send_join(request, roomId))
@server.route('/_matrix/federation/v1/make_join/<roomId>/<userId>') @server.route('/_matrix/federation/v1/make_join/<roomId>/<userId>')
def make_join(roomId, userId): def make_join(roomId, userId):
@@ -89,26 +315,31 @@ def room_directory():
return jsonify(room_dir_room) return jsonify(room_dir_room)
# https://spec.matrix.org/v1.15/server-server-api/#transactions # https://spec.matrix.org/v1.16/server-server-api/#transactions
@server.route('/_matrix/federation/v1/send/<txnId>', methods=['PUT']) @server.route('/_matrix/federation/v1/send/<txnId>', methods=['PUT'])
def federation_send(txnId): def receive_txn(txnId):
# This only works for v2 rooms, but anything
# bigger than v2 isn't real, so not a problem
# We will need to implement a way to store every # We will need to implement a way to store every
# event we need if we want to send events in the # event we need if we want to send events in the
# future. We don't send events currently, however. # future. We don't send events currently, however.
# These events are:
# - m.room.create
# - m.room.member
# - m.room.join_rules
# - m.room.power_levels
# - m.room.third_party_invite
#
# As per https://spec.matrix.org/v1.16/rooms/v2/#authorization-rules
data = request.data.decode('utf-8') data = request.data.decode('utf-8')
parsed_data = json.loads(data) parsed_data = json.loads(data)
response = {"pdus": {}} response = {"pdus": {}}
if 'pdus' in parsed_data and parsed_data['pdus']: if "pdus" in parsed_data:
for pdu in parsed_data['pdus']: for pdu in parsed_data["pdus"]:
if 'hashes' in pdu and 'sha256' in pdu['hashes']: if "event_id" in pdu:
pdu_hash = pdu['hashes']['sha256'] response["pdus"][pdu["event_id"]] = {}
response_key = f"${pdu_hash}:{parsed_data['origin']}"
response["pdus"][response_key] = {}
return jsonify(response) return jsonify(response)
@@ -120,8 +351,8 @@ def user_profile():
return jsonify({"avatar_url":f"mxc://{server_name}/cat"}) return jsonify({"avatar_url":f"mxc://{server_name}/cat"})
elif field == 'displayname': elif field == 'displayname':
return jsonify({"displayname":"Vona"}) return jsonify({"displayname":"Vona"})
else:
return jsonify({"errcode": "M_NOT_FOUND","error": "The requested profile key does not exist."}), 404 return jsonify({"errcode": "M_NOT_FOUND","error": "The requested profile key does not exist."}), 404
return jsonify({"avatar_url": f"mxc://{server_name}/cat","displayname": "Vona"}) return jsonify({"avatar_url": f"mxc://{server_name}/cat","displayname": "Vona"})
@@ -138,20 +369,20 @@ def user_keys():
return jsonify({"device_keys":{f"@vona:{server_name}":{}}}) return jsonify({"device_keys":{f"@vona:{server_name}":{}}})
# https://spec.matrix.org/v1.15/server-server-api/#inviting-to-a-room # https://spec.matrix.org/v1.16/server-server-api/#inviting-to-a-room
@server.route('/_matrix/federation/v2/invite/<room>/<txnId>', methods=['PUT']) @server.route('/_matrix/federation/v2/invite/<room>/<txnId>', methods=['PUT'])
def invite_user_v2(room, txnId): def invite_user_v2(room, txnId):
return invite_user(request.data.decode('utf-8'), 2) return invite_user(request.data)
@server.route('/_matrix/federation/v1/invite/<room>/<txnId>', methods=['PUT']) @server.route('/_matrix/federation/v1/invite/<room>/<txnId>', methods=['PUT'])
def invite_user_v1(room, txnId): def invite_user_v1(room, txnId):
return invite_user(request.data.decode('utf-8'), 1) return [200, invite_user(request.data)]
def invite_user(data, endpVer): def invite_user(data):
try: try:
invite_data = json.loads(data) invite_data = json.loads(data)
except: except:
return jsonify({"errcode":"M_NOT_JSON","error":"Content not JSON."}), 400 return jsonify({"errcode":"M_NOT_JSON","error":"Content not JSON."}),
if "event" in invite_data: if "event" in invite_data:
if "room_version" in invite_data: if "room_version" in invite_data:
@@ -161,22 +392,28 @@ def invite_user(data, endpVer):
"error": "Vona only supports room version 2.", "error": "Vona only supports room version 2.",
"room_version": invite_data["room_version"] "room_version": invite_data["room_version"]
}), 400 }), 400
else:
if "content" in invite_data["event"]: event = invite_data.get("event", {})
if "membership" in invite_data["event"]["content"] and invite_data["event"]["content"]["membership"] == "invite": content = event.get("content", {})
if endpVer == 1:
return jsonify([ # NOTE to crispycat: I know you loooooove this syntax
200, if (
{"event": globals.sign_json(invite_data["event"])} "content" in event
]) and "membership" in content
else: and "state_key" in event
return jsonify({"event": globals.sign_json(invite_data["event"]), "room_version": "2"}) and "room_id" in event
and content["membership"] == "invite"
and event["state_key"] == f"@vona:{server_name}"
):
return jsonify({"event": globals.sign_json_without_discard(invite_data["event"]), "room_version": "2"})
return jsonify({ return jsonify({
"errcode": "M_FORBIDDEN", "errcode": "M_FORBIDDEN",
"error": "Invalid invitation PDU" "error": "Invalid invitation PDU"
}), 403 }), 403
@server.route('/_matrix/federation/v1/hierarchy/<roomId>') @server.route('/_matrix/federation/v1/hierarchy/<roomId>')
def space_hierachy(roomId): def space_hierachy(roomId):
room = room_dir_room['chunk'][0] room = room_dir_room['chunk'][0]