Make Vona a Python module
This commit is contained in:
1
vona/__init__.py
Normal file
1
vona/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# read __main__.py
|
||||
106
vona/__main__.py
Normal file
106
vona/__main__.py
Normal file
@@ -0,0 +1,106 @@
|
||||
from flask import Flask, jsonify, request, redirect
|
||||
import vona.globals as globals
|
||||
from datetime import datetime
|
||||
import vona.config as config
|
||||
import logging
|
||||
|
||||
from vona.federation import server
|
||||
from vona.custom import custom
|
||||
from vona.identity import identity
|
||||
from vona.appservice import apps
|
||||
from vona.policy import policy
|
||||
from vona.client import client
|
||||
|
||||
logging.getLogger("werkzeug").disabled = True
|
||||
logging.getLogger("flask").disabled = True
|
||||
|
||||
app = Flask("vona")
|
||||
|
||||
app.register_blueprint(identity)
|
||||
app.register_blueprint(policy)
|
||||
app.register_blueprint(client)
|
||||
app.register_blueprint(custom)
|
||||
app.register_blueprint(server)
|
||||
app.register_blueprint(apps)
|
||||
|
||||
@app.before_request
|
||||
async def preflight():
|
||||
if request.method == "OPTIONS":
|
||||
return "", 204
|
||||
|
||||
@app.after_request
|
||||
async def handle_logging(response):
|
||||
response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate"
|
||||
response.headers["Access-Control-Allow-Origin"] = "*"
|
||||
response.headers["Access-Control-Allow-Headers"] = "*"
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, HEAD, POST, PUT, DELETE, OPTIONS"
|
||||
|
||||
if request.method == "OPTIONS":
|
||||
# Discard logs for OPTIONS
|
||||
return response
|
||||
|
||||
origin = "unknown"
|
||||
|
||||
try:
|
||||
if "Authorization" in request.headers:
|
||||
if request.headers["Authorization"].split()[0] == "X-Matrix":
|
||||
origin = request.headers["Authorization"].split('origin="')[1].split('"')[0]
|
||||
else:
|
||||
origin = "client"
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f'[{origin}] [{request.remote_addr}] [{datetime.now().strftime("%d/%b/%Y:%H:%M:%S")}] {request.method} {request.full_path} {response.status_code}')
|
||||
|
||||
return response
|
||||
|
||||
|
||||
# Landing page
|
||||
@app.route("/")
|
||||
async def root():
|
||||
return redirect("/_matrix/static/", 308)
|
||||
|
||||
@app.route("/_matrix/static/")
|
||||
async def matrix_static():
|
||||
return f'<!DOCTYPE html><html lang="en"><head><title>Vona {globals.vona_version} is running</title><style>body {{font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;max-width: 40em;margin: auto;text-align: center;}}h1,p {{margin: 1.5em;}}hr {{border: none;background-color: #ccc;color: #ccc;height: 1px;width: 7em;margin-top: 4em;}}.logo {{display: block;width: 12em;height: auto;margin: 4em auto;}}</style></head><body><img src="https://matrix.org/images/matrix-logo.svg" class="logo"><h1>It works! Vona {globals.vona_version} is running</h1><p>Your Vona server is listening on this port and is ready for messages.</p><p>To use this server you\"ll need <a href="https://matrix.org/ecosystem/clients/" target="_blank"rel="noopener noreferrer">a Matrix client</a>.</p><p>Welcome to the Matrix universe :)</p><hr><p><small><a href="https://natribu.org/en/" target="_blank" rel="noopener noreferrer">matrix.org</a></small></p></body></html>'
|
||||
|
||||
|
||||
# Error handlers
|
||||
@app.errorhandler(404)
|
||||
async def not_found(error):
|
||||
return jsonify({"errcode": "M_UNRECOGNIZED", "error": "Unrecognized request"}), 404
|
||||
|
||||
@app.errorhandler(405)
|
||||
async def invalid_request_method(error):
|
||||
return jsonify({"errcode": "M_UNRECOGNIZED", "error": "Unrecognized request"}), 405
|
||||
|
||||
@app.errorhandler(500)
|
||||
async def internal_error(error):
|
||||
return jsonify({"errcode": "M_UNKNOWN", "error": "Internal server error"}), 500
|
||||
|
||||
|
||||
# Well-known endpoints for federation,
|
||||
# clients, and support information.
|
||||
@app.route("/.well-known/matrix/server")
|
||||
async def server():
|
||||
return jsonify({"m.server": f"{config.server_name}:443"})
|
||||
|
||||
@app.route("/.well-known/matrix/support")
|
||||
async def support():
|
||||
if config.support:
|
||||
return jsonify(config.support)
|
||||
else:
|
||||
abort(404)
|
||||
|
||||
@app.route("/.well-known/matrix/client")
|
||||
async def client():
|
||||
return jsonify({
|
||||
"m.homeserver": {"base_url": f"https://{config.server_name}"},
|
||||
"m.identity_server": {"base_url": f"https://{config.server_name}"},
|
||||
})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host=config.addr, port=config.port)
|
||||
else:
|
||||
print("What the hell are you doing?")
|
||||
35
vona/appservice/__init__.py
Normal file
35
vona/appservice/__init__.py
Normal file
@@ -0,0 +1,35 @@
|
||||
from flask import Blueprint, jsonify
|
||||
from vona.config import the_funny_number
|
||||
import asyncio
|
||||
|
||||
apps = Blueprint("appservice", __name__)
|
||||
|
||||
# This implements both being a homeserver and
|
||||
# being an appservice. Why? Maximum carnage.
|
||||
|
||||
# Endpoints invoked by the homeserver are put
|
||||
# lower than ones invoked by the appservice.
|
||||
|
||||
@apps.route("/_matrix/client/v1/appservice/<app>/ping", methods=["POST"])
|
||||
async def homeserver_ping(app):
|
||||
# Sleeping here makes it more realistic
|
||||
await asyncio.sleep(the_funny_number / 1000)
|
||||
return jsonify({"duration_ms": the_funny_number})
|
||||
|
||||
|
||||
@apps.route("/_matrix/client/v3/directory/list/appservice/<net>/<room>", methods=["PUT"])
|
||||
@apps.route("/_matrix/app/v1/ping", methods=["POST"])
|
||||
@apps.route("/_matrix/app/v1/transactions/<txnId>", methods=["PUT"])
|
||||
@apps.route("/_matrix/app/v1/thirdparty/protocol/<protocol>")
|
||||
@apps.route("/_matrix/app/v1/rooms/<room>")
|
||||
@apps.route("/_matrix/app/v1/users/<user>")
|
||||
async def empty_dict(**kwargs):
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@apps.route("/_matrix/app/v1/thirdparty/location")
|
||||
@apps.route("/_matrix/app/v1/thirdparty/location/<protocol>")
|
||||
@apps.route("/_matrix/app/v1/thirdparty/user")
|
||||
@apps.route("/_matrix/app/v1/thirdparty/user/<protocol>")
|
||||
async def empty_array(**kwargs):
|
||||
return jsonify([])
|
||||
657
vona/client/__init__.py
Normal file
657
vona/client/__init__.py
Normal file
@@ -0,0 +1,657 @@
|
||||
from flask import Blueprint, jsonify, request, send_file
|
||||
from vona.federation import send_join
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
import asyncio
|
||||
import random
|
||||
import os
|
||||
|
||||
client = Blueprint("c2s", __name__)
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/account/password", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/user/<user>/account_data/<type>", methods=["GET", "PUT"])
|
||||
@client.route("/_matrix/client/r0/user/<user>/account_data/<type>", methods=["GET", "PUT"])
|
||||
@client.route("/_matrix/client/v3/sendToDevice/<event>/<txnId>", methods=["PUT"])
|
||||
@client.route("/_matrix/media/v3/upload/<server>/<media>", methods=["PUT"])
|
||||
@client.route("/_matrix/client/v3/thirdparty/protocols")
|
||||
@client.route("/_matrix/client/r0/thirdparty/protocols")
|
||||
@client.route("/_matrix/client/v3/delete_devices", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/delete_devices", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/logout/all", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/logout", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/logout", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/invite", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<roomId>/leave", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/rooms/<roomId>/leave", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<roomId>/read_markers", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/rooms/<roomId>/read_markers", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/typing/<user>", methods=["PUT"])
|
||||
@client.route("/_matrix/client/v3/keys/device_signing/upload", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/receipt/<type>/<event>", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/users/<user>/report", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/voip/turnServer")
|
||||
@client.route("/_matrix/client/r0/voip/turnServer")
|
||||
@client.route("/_matrix/client/v3/rooms/<r>/report/<e>")
|
||||
@client.route("/_matrix/client/v3/rooms/<r>/report")
|
||||
@client.route("/_matrix/client/v3/users/<u>/report")
|
||||
async def empty_response(**kwargs):
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/versions")
|
||||
async def spec_versions():
|
||||
return jsonify({
|
||||
"versions": (
|
||||
["r0.0.0"] + [f"r0.{i}.0" for i in range(1, 7)] +
|
||||
["r0.6.1"] + [f"v1.{i}" for i in range(1, 17)]
|
||||
),
|
||||
"unstable_features": {
|
||||
"uk.half-shot.msc2666": True,
|
||||
"uk.timedout.msc4323": True
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/admin/whois/<user>")
|
||||
@client.route("/_matrix/client/r0/admin/whois/<user>")
|
||||
async def whois(user):
|
||||
if userId.startswith("@"):
|
||||
return jsonify({
|
||||
"devices": {
|
||||
"": {
|
||||
"sessions": [{
|
||||
"connections": [{
|
||||
"ip": "127.0.0.1",
|
||||
"last_seen": config.the_funny_number,
|
||||
"user_agent": f"Vona/{globals.vona_version}"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
},
|
||||
"user_id": user
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
"errcode": "M_INVALID_PARAM",
|
||||
"error": "Expected UserID string to start with '@'"
|
||||
})
|
||||
|
||||
@client.route("/_matrix/client/unstable/uk.timedout.msc4323/admin/suspend/<user>", methods=["GET", "PUT"])
|
||||
@client.route("/_matrix/client/v1/admin/suspend/<user>", methods=["GET", "PUT"])
|
||||
async def suspend(user):
|
||||
if request.method == "PUT":
|
||||
req = request.get_json()
|
||||
if req and "suspended" in req:
|
||||
return jsonify({"suspended": req["suspended"]})
|
||||
|
||||
return jsonify({"suspended": True})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/unstable/uk.timedout.msc4323/admin/lock/<user>", methods=["GET", "PUT"])
|
||||
@client.route("/_matrix/client/v1/admin/lock/<user>", methods=["GET", "PUT"])
|
||||
async def lock(user):
|
||||
if request.method == "PUT":
|
||||
req = request.get_json()
|
||||
if req and "locked" in req:
|
||||
return jsonify({"locked": req["locked"]})
|
||||
|
||||
return jsonify({"locked": True})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/rooms/<roomId>/members")
|
||||
@client.route("/_matrix/client/r0/rooms/<roomId>/members")
|
||||
async def room_member_count(roomId):
|
||||
return jsonify({
|
||||
"chunk": [{
|
||||
"content": {
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"displayname": "Vona",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": globals.make_event_id(),
|
||||
"origin_server_ts": config.the_funny_number,
|
||||
"room_id": roomId,
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": f"@vona:{config.server_name}",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {}
|
||||
}]
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/account/whoami")
|
||||
async def whoami():
|
||||
return jsonify({
|
||||
"device_id": "VVOONNAA",
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/register", methods=["POST"])
|
||||
@client.route("/_matrix/client/v1/register", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/register", methods=["POST"])
|
||||
async def register():
|
||||
if config.users_can_register:
|
||||
try:
|
||||
data = request.get_json()
|
||||
|
||||
if data and "auth" in data:
|
||||
return jsonify({
|
||||
"user_id": f"@vona:{config.server_name}",
|
||||
"home_server": f"{config.server_name}",
|
||||
"access_token": "vona",
|
||||
"device_id": "VVOONNAA"
|
||||
})
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
return jsonify({
|
||||
"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/r0/login", methods=["GET", "POST"])
|
||||
@client.route("/_matrix/client/v3/login", methods=["GET", "POST"])
|
||||
async def login():
|
||||
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({
|
||||
"access_token": "vona",
|
||||
"device_id": "VVOONNAA",
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
})
|
||||
|
||||
@client.route("/_matrix/client/v3/account/password/email/requestToken", methods=["POST"])
|
||||
async def pswd_reset():
|
||||
return jsonify({"errcode":"M_THREEPID_NOT_FOUND","error":"Email not found"}), 400
|
||||
|
||||
@client.route("/_matrix/client/v3/keys/upload", methods=["POST"])
|
||||
async def key_upload():
|
||||
return jsonify({"one_time_key_counts":{"signed_curve25519":50}})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/room_keys/version", methods=["POST", "GET"])
|
||||
@client.route("/_matrix/client/unstable/room_keys/version", methods=["POST", "GET"])
|
||||
async def room_keys():
|
||||
if request.method == "POST":
|
||||
return jsonify({"version": str(config.the_funny_number)})
|
||||
|
||||
return jsonify({
|
||||
"algorithm": "m.megolm_backup.v1.curve25519-aes-sha2",
|
||||
"auth_data": {
|
||||
"public_key":"vonaisflazingbastandsemorymafe",
|
||||
"signatures": {
|
||||
f"@vona:{config.server_name}": {
|
||||
# TODO: Make this actually valid
|
||||
"ed25519:vonaa": "vona"
|
||||
}
|
||||
}
|
||||
},
|
||||
"count": config.the_funny_number,
|
||||
"etag": "burgerkingfootlettuce",
|
||||
"version": str(config.the_funny_number)
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/capabilities")
|
||||
@client.route("/_matrix/client/r0/capabilities")
|
||||
async def capabilities():
|
||||
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",
|
||||
"9": "stable",
|
||||
"10": "stable",
|
||||
"11": "stable",
|
||||
"12": "stable"
|
||||
},
|
||||
"org.matrix.msc3244.room_capabilities": {
|
||||
"knock": {
|
||||
"preferred":"7",
|
||||
"support": [
|
||||
"7","8","9","10","11","12"
|
||||
]
|
||||
},
|
||||
"restricted": {
|
||||
"preferred": "9",
|
||||
"support": ["8","9","10","11","12"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"m.change_password": {"enabled": True},
|
||||
"m.3pid_changes": {"enabled": True},
|
||||
"m.get_login_token": {"enabled": False},
|
||||
"m.profile_fields": {
|
||||
"enabled": True,
|
||||
"allowed":["*"]
|
||||
},
|
||||
"account_moderation": {
|
||||
"suspend": True,
|
||||
"lock": True
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/r0/pushrules/")
|
||||
@client.route("/_matrix/client/v3/pushrules/")
|
||||
async def pushrules():
|
||||
# TODO: Actually implement this
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/user/<user>/filter/<data>")
|
||||
@client.route("/_matrix/client/r0/user/<user>/filter/<data>")
|
||||
@client.route("/_matrix/client/v3/user/<user>/filter", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/user/<user>/filter", methods=["POST"])
|
||||
async def filter(**kwargs):
|
||||
return jsonify({"filter_id": "vvvooonnnaaa"})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/join/<room>", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/join/<room>", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/join", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/knock/<room>", methods=["POST"])
|
||||
async def join(room):
|
||||
return jsonify({"room_id": room})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/initialSync")
|
||||
@client.route("/_matrix/client/v3/sync")
|
||||
@client.route("/_matrix/client/r0/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": config.the_funny_number,
|
||||
"sender": f"@vona:{config.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
|
||||
if "timeout" in request.args:
|
||||
try:
|
||||
wait_time = int(request.args.get("timeout")) / 1000
|
||||
except:
|
||||
pass
|
||||
await asyncio.sleep(wait_time)
|
||||
|
||||
return jsonify({
|
||||
"next_batch": f"{os.urandom(64).hex()}",
|
||||
"presence": {},
|
||||
"device_one_time_keys_count": {"signed_curve25519": 50},
|
||||
"org.matrix.msc2732.device_unused_fallback_key_types": ["signed_curve25519"],
|
||||
"device_unused_fallback_key_types": ["signed_curve25519"],
|
||||
"rooms": {
|
||||
"join": {
|
||||
room: {
|
||||
"timeline": {
|
||||
"events": [],
|
||||
"prev_batch": f"{random.randint(32095,309390)}",
|
||||
"limited": False
|
||||
},
|
||||
"state": {"events": room_state},
|
||||
"account_data": {"events": []},
|
||||
"ephemeral": {"events": []},
|
||||
"unread_notifications": {
|
||||
"notification_count": 0,
|
||||
"highlight_count": 0
|
||||
},
|
||||
"summary": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/send/<eventType>/<txnId>", methods=["POST", "PUT"])
|
||||
@client.route("/_matrix/client/r0/rooms/<room>/send/<eventType>/<txnId>", methods=["POST", "PUT"])
|
||||
async def send_message(room, eventType, txnId):
|
||||
return jsonify({"event_id": globals.make_event_id()}), 200
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/user_directory/search", methods=["POST"])
|
||||
async def user_directory():
|
||||
return jsonify({
|
||||
"limited": False,
|
||||
"results": [{
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"display_name": "Vona",
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
}]
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/devices")
|
||||
@client.route("/_matrix/client/r0/devices")
|
||||
async def devices():
|
||||
return jsonify({
|
||||
"devices": [{
|
||||
"device_id": "VVOONNAA",
|
||||
"display_name": "Vona",
|
||||
"last_seen_ip": "127.0.0.1",
|
||||
"last_seen_ts": config.the_funny_number
|
||||
}]
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/devices/<device>", methods=["GET", "PUT", "DELETE"])
|
||||
@client.route("/_matrix/client/r0/devices/<device>", methods=["GET", "PUT", "DELETE"])
|
||||
async def get_device(device):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"device_id": device,
|
||||
"display_name": "Vona",
|
||||
"last_seen_ip": "127.0.0.1",
|
||||
"last_seen_ts": config.the_funny_number
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/refresh", methods=["POST"])
|
||||
async def refresh():
|
||||
return jsonify({
|
||||
"access_token": "vona",
|
||||
"expires_in_ms": config.the_funny_number * 1000,
|
||||
"refresh_token": "vona"
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/unstable/im.nheko.summary/rooms/<roomId>/summary")
|
||||
@client.route("/_matrix/client/unstable/im.nheko.summary/summary/<roomId>")
|
||||
@client.route("/_matrix/client/v1/room_summary/<roomId>")
|
||||
async def room_summary(roomId):
|
||||
return jsonify({
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"guest_can_join": False,
|
||||
"name": "Vona",
|
||||
"num_joined_members": config.the_funny_number,
|
||||
"topic": None,
|
||||
"world_readable": False,
|
||||
"join_rule": "public",
|
||||
"room_type": "m.room",
|
||||
"membership": "join",
|
||||
"room_version": 2
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/directory/room/<room>", methods=["GET", "PUT", "DELETE"])
|
||||
@client.route("/_matrix/client/r0/directory/room/<room>")
|
||||
async def room_query(room):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"servers": [config.server_name]
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@client.route("/_matrix/client/v3/rooms/<room>/aliases")
|
||||
async def room_aliases(room):
|
||||
return jsonify({
|
||||
"aliases": [
|
||||
f"#vona:{config.server_name}"
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/directory/list/room/<room>", methods=["GET", "PUT"])
|
||||
@client.route("/_matrix/client/r0/directory/list/room/<room>", methods=["GET", "PUT"])
|
||||
async def room_visibility(room):
|
||||
return jsonify({"visibility": "public"})
|
||||
|
||||
@client.route("/_matrix/client/v3/search", methods=["POST"])
|
||||
async def search():
|
||||
room = globals.make_event_id().replace("$", "!")
|
||||
event = globals.make_event_id()
|
||||
|
||||
return jsonify({
|
||||
"search_categories": {
|
||||
"room_events": {
|
||||
"count": 1,
|
||||
"groups": {
|
||||
"room_id": {
|
||||
room: {
|
||||
"next_batch": "vona",
|
||||
"order": 1,
|
||||
"results": [event]
|
||||
}
|
||||
}
|
||||
},
|
||||
"highlights": [],
|
||||
"next_batch": "vona",
|
||||
"results": [{
|
||||
"rank": config.the_funny_number,
|
||||
"result": {
|
||||
"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 "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."
|
||||
},
|
||||
"event_id": event,
|
||||
"origin_server_ts": config.the_funny_number,
|
||||
"room_id": room,
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"type": "m.room.message"
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/media/v1/thumbnail/<server>/<file>")
|
||||
@client.route("/_matrix/client/v1/media/thumbnail/<s>/<f>")
|
||||
@client.route("/_matrix/media/r0/thumbnail/<server>/<file>")
|
||||
@client.route("/_matrix/media/v3/thumbnail/<server>/<media>")
|
||||
@client.route("/_matrix/media/v3/download/<server>/<media>/<file>")
|
||||
@client.route("/_matrix/client/v1/media/download/<s>/<f>")
|
||||
@client.route("/_matrix/media/v3/download/<server>/<media>")
|
||||
@client.route("/_matrix/media/r0/download/<server>/<media>")
|
||||
async def media(**kwargs):
|
||||
return send_file(config.cat)
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/register/available")
|
||||
@client.route("/_matrix/client/r0/register/available")
|
||||
async def username_available():
|
||||
return jsonify({"available": True})
|
||||
|
||||
|
||||
@client.route("/_matrix/media/v3/preview_url")
|
||||
async def url_preview():
|
||||
return jsonify({
|
||||
"matrix:image:size": 102400,
|
||||
"og:description": "look at this cool cat",
|
||||
"og:image": f"mxc://{config.server_name}/ascERGshawAWawugaAcauga",
|
||||
"og:image:height": 48,
|
||||
"og:image:type": "image/jpg",
|
||||
"og:image:width": 48,
|
||||
"og:title": "cool cat"
|
||||
})
|
||||
|
||||
@client.route("/_matrix/client/v1/media/preview_url")
|
||||
async def media_preview():
|
||||
response = send_file(config.cat)
|
||||
response.headers["Content-Disposition"] = f'inline; filename="cat.jpg"'
|
||||
response.headers["Content-Type"] = "image/jpg"
|
||||
return response
|
||||
|
||||
@client.route("/_matrix/media/v3/upload", methods=["POST"])
|
||||
@client.route("/_matrix/media/r0/upload", methods=["POST"])
|
||||
@client.route("/_matrix/media/v1/create", methods=["POST"])
|
||||
async def upload_media():
|
||||
return jsonify({"content_uri": f"mxc://{config.server_name}/cat"})
|
||||
|
||||
|
||||
@client.route("/_matrix/media/v3/config")
|
||||
async def media_config():
|
||||
return jsonify({"m.upload.size": config.the_funny_number * 69420})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/profile/<userId>/<key>", methods=["GET", "PUT", "DELETE"])
|
||||
@client.route("/_matrix/client/r0/profile/<userId>/<key>", methods=["GET", "PUT", "DELETE"])
|
||||
async def profile_keys(userId, key):
|
||||
if request.method == "GET":
|
||||
if key == "avatar_url":
|
||||
return jsonify({"avatar_url": f"mxc://{config.server_name}/cat"})
|
||||
elif key == "displayname":
|
||||
return jsonify({"displayname": "Vona"})
|
||||
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "The requested profile key does not exist."
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@client.route("/_matrix/client/v3/profile/<userId>")
|
||||
@client.route("/_matrix/client/r0/profile/<userId>")
|
||||
async def user_profile(userId):
|
||||
return jsonify({
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"displayname": "Vona"
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/rooms/<roomId>/messages")
|
||||
@client.route("/_matrix/client/r0/rooms/<roomId>/messages")
|
||||
async def room_messages(roomId):
|
||||
return jsonify({
|
||||
"chunk": [{
|
||||
"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 "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."
|
||||
},
|
||||
"event_id": globals.make_event_id(),
|
||||
"origin_server_ts": config.the_funny_number,
|
||||
"room_id": roomId,
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"type": "m.room.message"
|
||||
}],
|
||||
"end": f"{os.urandom(16).hex()}",
|
||||
"start": f"{os.urandom(16).hex()}"
|
||||
})
|
||||
|
||||
@client.route("/_matrix/client/v3/keys/query", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/keys/query", methods=["POST"])
|
||||
async def query_keys():
|
||||
user = request.get_json()["device_keys"]
|
||||
return jsonify({
|
||||
"device_keys": user,
|
||||
"master_keys": user,
|
||||
"self_signing_keys": user,
|
||||
"user_signing_keys": user
|
||||
})
|
||||
|
||||
@client.route("/_matrix/client/api/v1/createRoom", methods=["POST"])
|
||||
@client.route("/_matrix/client/v3/createRoom", methods=["POST"])
|
||||
@client.route("/_matrix/client/r0/createRoom", methods=["POST"])
|
||||
async def create_room():
|
||||
return jsonify({"room_id": globals.make_event_id().replace("$", "!")})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/unstable/uk.half-shot.msc2666/mutual_rooms")
|
||||
@client.route("/_matrix/client/v1/user/mutual_rooms")
|
||||
async def mutual_rooms():
|
||||
return jsonify({
|
||||
"joined": [
|
||||
globals.make_event_id().replace("$", "!")
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/r0/presence/<user>/status", methods=["GET", "PUT"])
|
||||
async def presence(user):
|
||||
if request.method == "PUT":
|
||||
return jsonify({})
|
||||
|
||||
return jsonify({
|
||||
"presence": "online"
|
||||
})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/r0/publicRooms", methods=["GET", "POST"])
|
||||
async def room_directory():
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"total_room_count_estimate": 0
|
||||
})
|
||||
124
vona/config/__init__.py
Normal file
124
vona/config/__init__.py
Normal file
@@ -0,0 +1,124 @@
|
||||
import os
|
||||
from pathlib import Path
|
||||
import tomllib
|
||||
import mimetypes
|
||||
|
||||
addr: str = "127.0.0.1"
|
||||
port: int = 5000
|
||||
allow_registration: bool = False
|
||||
the_funny_number: int = 1337
|
||||
cat: str = "/etc/vona/cat.jpg"
|
||||
|
||||
server_name: str = ""
|
||||
signing_key: str = ""
|
||||
support: dict = {"contacts": []}
|
||||
|
||||
_CONFIG_PATH = Path("/etc/vona/config.toml")
|
||||
|
||||
|
||||
def _fatal(msg: str) -> None:
|
||||
print(f"[FATL] {msg}")
|
||||
os._exit(1)
|
||||
|
||||
|
||||
def _warn(msg: str) -> None:
|
||||
print(f"[WARN] {msg}")
|
||||
|
||||
|
||||
def _load_toml(path: Path) -> dict:
|
||||
try:
|
||||
with path.open("rb") as f:
|
||||
return tomllib.load(f)
|
||||
except FileNotFoundError:
|
||||
_fatal(f"[FATL] Configuration file not found at {path}")
|
||||
except PermissionError:
|
||||
_fatal(f"[FATL] Permission denied when accessing configuration {path}")
|
||||
except tomllib.TOMLDecodeError as e:
|
||||
_fatal(f"[FATL] Invalid TOML configuration: {e}")
|
||||
|
||||
|
||||
def _read_signing_key_from_path(path_value) -> str | None:
|
||||
p = Path(path_value)
|
||||
if not p.exists():
|
||||
_fatal(f"[FATL] signing_key_path {p} does not exist")
|
||||
try:
|
||||
return p.read_text(encoding="utf-8").strip()
|
||||
except Exception as e:
|
||||
_fatal(f"[FATL] Failed to read signing_key_path {p}: {e}")
|
||||
|
||||
|
||||
def _validate_cat_path(cat_path: str) -> Path:
|
||||
p = Path(cat_path)
|
||||
if not p.exists():
|
||||
_fatal(f"[FATL] Cat photo at {p} does not exist")
|
||||
|
||||
mtype, _ = mimetypes.guess_type(str(p))
|
||||
if mtype is None or not mtype.startswith("image/"):
|
||||
_warn(f"[WARN] Cat file {p} does not look like an image (mimetype={mtype})")
|
||||
|
||||
return p
|
||||
|
||||
|
||||
def _apply_config(cfg: dict) -> None:
|
||||
global addr, port, allow_registration, server_name, signing_key, cat, support
|
||||
|
||||
if "address" in cfg:
|
||||
addr = str(cfg["address"])
|
||||
|
||||
if "port" in cfg:
|
||||
try:
|
||||
port = int(cfg["port"])
|
||||
except (TypeError, ValueError):
|
||||
_warn(
|
||||
f"[WARN] Invalid port in config: {cfg.get('port')}; using default {port}"
|
||||
)
|
||||
|
||||
if "allow_registration" in cfg:
|
||||
allow_registration = bool(cfg["allow_registration"])
|
||||
|
||||
if "server_name" in cfg:
|
||||
server_name = str(cfg["server_name"])
|
||||
else:
|
||||
_fatal("[FATL] `server_name` is not in configuration")
|
||||
|
||||
if "signing_key" in cfg and "signing_key_path" in cfg:
|
||||
_warn(
|
||||
"[WARN] Both `signing_key` and `signing_key_path` present. Using `signing_key`."
|
||||
)
|
||||
|
||||
if "signing_key" in cfg:
|
||||
signing_key = str(cfg["signing_key"]).strip()
|
||||
elif "signing_key_path" in cfg:
|
||||
sk = _read_signing_key_from_path(cfg["signing_key_path"])
|
||||
if sk:
|
||||
signing_key = sk
|
||||
else:
|
||||
_fatal(
|
||||
"[FATL] `signing_key` is not in configuration. "
|
||||
"A signing key can be generated using `cmd/generate_key.py`."
|
||||
)
|
||||
|
||||
if "cat" in cfg:
|
||||
cat = str(cfg["cat"])
|
||||
|
||||
cat_path = _validate_cat_path(cat)
|
||||
cat = str(cat_path)
|
||||
|
||||
support_obj = {"contacts": []}
|
||||
if "support" in cfg and isinstance(cfg["support"], dict):
|
||||
_support = cfg["support"]
|
||||
contact = {"role": "m.role.admin"}
|
||||
if "mxid" in _support:
|
||||
contact["matrix_id"] = str(_support["mxid"])
|
||||
if "email" in _support:
|
||||
contact["email_address"] = str(_support["email"])
|
||||
if len(contact) > 1:
|
||||
support_obj["contacts"].append(contact)
|
||||
else:
|
||||
_warn("[WARN] No support contacts are defined")
|
||||
support = support_obj
|
||||
|
||||
print("[INFO] Configuration file was valid")
|
||||
|
||||
|
||||
_apply_config(_load_toml(_CONFIG_PATH))
|
||||
1
vona/config/__main__.py
Normal file
1
vona/config/__main__.py
Normal file
@@ -0,0 +1 @@
|
||||
import vona.config
|
||||
464
vona/custom/__init__.py
Normal file
464
vona/custom/__init__.py
Normal file
@@ -0,0 +1,464 @@
|
||||
from flask import Blueprint, jsonify, request, Response
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
import base64
|
||||
import re
|
||||
import os
|
||||
|
||||
custom = Blueprint("custom", __name__)
|
||||
|
||||
# This implements custom endpoints
|
||||
# used by other homeserver
|
||||
# implementations. They do not start
|
||||
# with /_matrix/
|
||||
|
||||
# This should be split into more
|
||||
# files eventually.
|
||||
|
||||
|
||||
@custom.route("/_synapse/admin/v1/suspend/<user_id>", methods=["PUT"])
|
||||
@custom.route("/_synapse/admin/v1/deactivate/<user_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/reset_password/<user_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/admin", methods=["PUT"])
|
||||
@custom.route("/_synapse/admin/v2/users/<user_id>/delete_devices", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/shadow_ban", methods=["DELETE", "POST"])
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/override_ratelimit", methods=["GET", "POST", "DELETE"])
|
||||
@custom.route("/_synapse/admin/v1/media/protect/<media_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/media/unprotect/<media_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/media/quarantine/<s>/<media_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/media/unquarantine/<s>/<media_id>", methods=["POST"])
|
||||
@custom.route("/_dendrite/admin/purgeRoom/<roomId>", methods=["POST"])
|
||||
@custom.route("/_dendrite/admin/refreshDevices/<userId>", methods=["POST"])
|
||||
@custom.route("/_dendrite/admin/fulltext/reindex")
|
||||
@custom.route("/_synapse/admin/v1/federation/destinations/<destination>/reset_connection", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room>")
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>/timestamp_to_event")
|
||||
@custom.route("/_synapse/admin/v2/rooms/delete_status/<delete_id>")
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id_or_alias>/make_room_admin", methods=["POST"])
|
||||
async def empty_response(**kwargs):
|
||||
return jsonify({})
|
||||
|
||||
|
||||
# Synapse
|
||||
@custom.route("/_synapse/admin/v1/server_version")
|
||||
async def synapse_version():
|
||||
return jsonify({"server_version": globals.vona_version})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/users")
|
||||
async def synapse_user_list():
|
||||
return jsonify({
|
||||
"users": [
|
||||
{
|
||||
"name": f"@vona:{config.server_name}",
|
||||
"is_guest": 0,
|
||||
"admin": 0,
|
||||
"user_type": "vona",
|
||||
"deactivated": 0,
|
||||
"erased": False,
|
||||
"shadow_banned": 0,
|
||||
"displayname": "Vona",
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"creation_ts": config.the_funny_number,
|
||||
"locked": False
|
||||
}
|
||||
],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/users/<user_id>", methods=["GET", "PUT"])
|
||||
async def synapse_user_info(user_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"name": f"@vona:{config.server_name}",
|
||||
"displayname": "Vona",
|
||||
"threepids": [],
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"is_guest": 0,
|
||||
"admin": 0,
|
||||
"deactivated": 0,
|
||||
"erased": False,
|
||||
"shadow_banned": 0,
|
||||
"creation_ts": config.the_funny_number,
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"appservice_id": config.the_funny_number,
|
||||
"consent_server_notice_sent":config.the_funny_number,
|
||||
"consent_version": config.the_funny_number,
|
||||
"consent_ts": config.the_funny_number,
|
||||
"external_ids": [],
|
||||
"user_type": "vona",
|
||||
"locked": False,
|
||||
"suspended": False
|
||||
})
|
||||
|
||||
return jsonify({}), 201
|
||||
|
||||
@custom.route("/_synapse/admin/v1/whois/<user_id>")
|
||||
async def synapse_whois(user_id):
|
||||
return jsonify({
|
||||
"user_id": f"@vona:{config.server_name}",
|
||||
"devices": {
|
||||
"": {
|
||||
"sessions": [{
|
||||
"connections": [{
|
||||
"ip":f"127.0.0.1",
|
||||
"last_seen":config.the_funny_number,
|
||||
"user_agent":f"Vona/{globals.vona_version}"
|
||||
}]
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/joined_rooms")
|
||||
async def synapse_user_joined_rooms(user_id):
|
||||
return jsonify({
|
||||
"joined_rooms": [globals.make_event_id().replace("$", "!")],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/sent_invite_count")
|
||||
async def synapse_invite_count(user_id):
|
||||
return jsonify({"invite_count": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/accountdata")
|
||||
async def synapse_account_data(user_id):
|
||||
return jsonify({"account_data":{"global":{}}})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/media", methods=["GET", "DELETE"])
|
||||
async def synapse_account_media(user_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({"media": [{"created_ts":config.the_funny_number,"last_access_ts":config.the_funny_number,"media_id":"cat","media_length":config.the_funny_number,"media_type":"image/jpeg","quarantined_by":"null","safe_from_quarantine":False,"upload_name":"cat.jpg"}], "total": config.the_funny_number})
|
||||
|
||||
return jsonify({"deleted_media": ["cat"], "total": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/login", methods=["POST"])
|
||||
async def synapse_account_login(user_id):
|
||||
return jsonify({"access_token": "vona"})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/_allow_cross_signing_replacement_without_uia", methods=["POST"])
|
||||
async def synapse_stupid_mas_bullshit(user_id):
|
||||
return jsonify({"updatable_without_uia_before_ms": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/users/<user_id>/devices", methods=["GET", "POST"])
|
||||
async def synapse_device_list(user_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"devices": [{
|
||||
"device_id": "VVOONNAA",
|
||||
"display_name": "Vona",
|
||||
"last_seen_ip": "127.0.0.1",
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"last_seen_user_agent": f"Vona/{globals.vona_version}"
|
||||
}],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/users/<user_id>/devices/<device_id>", methods=["GET", "PUT", "DELETE"])
|
||||
async def synapse_device_info(user_id, device_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"device_id": "VVOONNAA",
|
||||
"display_name": "Vona",
|
||||
"last_seen_ip": "127.0.0.1",
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"last_seen_user_agent": f"Vona/{globals.vona_version}"
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/users/<user_id>/pushers")
|
||||
async def synapse_pushers(user_id):
|
||||
return jsonify({"pushers": [], "total": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/username_available")
|
||||
async def synapse_username_available():
|
||||
return jsonify({"available": True})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/threepid/<medium>/users/<addr>")
|
||||
@custom.route("/_synapse/admin/v1/auth_providers/<provider>/users/<ext>")
|
||||
async def synapse_threepid(p, a):
|
||||
return jsonify({"user_id": f"@vona:{config.server_name}"})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/<user_id>/redact")
|
||||
def synapse_redact(user_id):
|
||||
return jsonify({"redact_id": os.urandom(16).hex()})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/user/redact_status/<redact_id>")
|
||||
async def synapse_redact_status(redact_id):
|
||||
return jsonify({"status":"active","failed_redactions":[]})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/experimental_features/<user_id>", methods=["GET", "PUT"])
|
||||
async def synapse_experimental_features(user_id):
|
||||
return jsonify({"features": {}})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/register", methods=["GET", "POST"])
|
||||
async def synapse_register():
|
||||
if request.method == "GET":
|
||||
return jsonify({"nonce": os.urandom(16).hex()})
|
||||
|
||||
return jsonify({"access_token": "vona"})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/join/<roomId>", methods=["POST"])
|
||||
async def synapse_membership_manipulation(roomId):
|
||||
return jsonify({"room_id": globals.make_event_id().replace("$", "!")})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/account_validity/validity", methods=["POST"])
|
||||
async def synapse_account_validity():
|
||||
return jsonify({"expiration_ts": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/send_server_notice", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/send_server_notice/<txnId>", methods=["PUT"])
|
||||
async def synapse_server_notice(**kwargs):
|
||||
return jsonify({"event_id": globals.make_event_id()})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/purge_history/<room_id>/<event_id>", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/purge_history/<room_id>", methods=["POST"])
|
||||
async def synapse_purge_event(**kwargs):
|
||||
return jsonify({"purge_id": os.urandom(16).hex()})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/purge_history_status/<purge_id>")
|
||||
async def synapse_purge_status(purge_id):
|
||||
return jsonify({"status":"active"})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/room/<room_id>/media")
|
||||
async def synapse_room_media(room_id):
|
||||
return jsonify({"local": [f"mxc://{config.server_name}/cat"], "remote": []})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/room/<room_id>/media/quarantine", methods=["POST"])
|
||||
async def synapse_quarantine_room_media(room_id):
|
||||
return jsonify({"num_quarantined": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/media/<s>/delete", methods=["POST"])
|
||||
@custom.route("/_synapse/admin/v1/media/<s>/<media_id>", methods=["DELETE"])
|
||||
@custom.route("/_synapse/admin/v1/media/delete", methods=["POST"])
|
||||
async def synapse_delete_media_from_server(**kwargs):
|
||||
return jsonify({"deleted_media": ["cat"], "total": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/purge_media_cache", methods=["POST"])
|
||||
async def synapse_delete_remote_media():
|
||||
return jsonify({"deleted": config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/statistics/users/media")
|
||||
async def synapse_media_stats():
|
||||
return jsonify({"users":[{"displayname":"Vona","media_count":config.the_funny_number,"media_length":config.the_funny_number,"user_id":f"@vona:{config.server_name}"}],"total":config.the_funny_number})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/statistics/database/rooms")
|
||||
async def synapse_room_stats():
|
||||
return jsonify({
|
||||
"rooms": [{
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"estimated_size": config.the_funny_number * 420
|
||||
}]
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/background_updates/enabled", methods=["POST", "GET"])
|
||||
@custom.route("/_synapse/admin/v1/background_updates/status")
|
||||
async def synapse_change_bg_update():
|
||||
return jsonify({"enabled":False})
|
||||
|
||||
# No documentation on what Synapse actually returns for this API, so a blank dict for now
|
||||
@custom.route("/_synapse/admin/v1/background_updates/start_job", methods=["POST"])
|
||||
async def synapse_bg_update_start_job():
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@custom.route("/_synapse/admin/v1/event_reports")
|
||||
async def synapse_event_reports():
|
||||
return jsonify({
|
||||
"event_reports": [{
|
||||
"event_id": globals.make_event_id(),
|
||||
"id": config.the_funny_number,
|
||||
"reason": "",
|
||||
"score": config.the_funny_number,
|
||||
"received_ts": config.the_funny_number,
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"name": "Vona",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"user_id": f"@vona:{config.server_name}"
|
||||
}],
|
||||
"total": config.the_funny_number
|
||||
})
|
||||
|
||||
|
||||
@custom.route("/_synapse/admin/v1/event_reports/<report_id>", methods=["GET", "DELETE"])
|
||||
async def synapse_interact_with_reported_event(report_id):
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"event_id": globals.make_event_id(),
|
||||
"event_json": globals.hash_and_sign_event({
|
||||
"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 "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."
|
||||
},
|
||||
"depth": config.the_funny_number,
|
||||
"origin": config.server_name,
|
||||
"origin_server_ts": config.the_funny_number,
|
||||
"prev_events": [globals.make_event_id()],
|
||||
"prev_state": [],
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"type": "m.room.message"
|
||||
})
|
||||
})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/federation/destinations")
|
||||
async def synapse_federation_destinations():
|
||||
return jsonify({
|
||||
"destinations": [{}],
|
||||
"total": 0
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/federation/destinations/<destination>")
|
||||
async def synapse_destination(destination):
|
||||
return jsonify({
|
||||
"destination": destination,
|
||||
"retry_last_ts": config.the_funny_number,
|
||||
"retry_interval": config.the_funny_number,
|
||||
"failure_ts": config.the_funny_number,
|
||||
"last_successful_stream_ordering": None
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/federation/destinations/<destination>/rooms")
|
||||
async def synapse_destination_rooms(destination):
|
||||
return jsonify({
|
||||
"rooms": [],
|
||||
"total": 0
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/registration_tokens")
|
||||
async def synapse_reg_tokens():
|
||||
return jsonify({
|
||||
"registration_tokens": [{
|
||||
"token": "Vona",
|
||||
"uses_allowed": config.the_funny_number,
|
||||
"pending": 0,
|
||||
"completed": 1,
|
||||
"expiry_time": None
|
||||
}]
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/registration_tokens/<token>", methods=["GET", "PUT", "DELETE"])
|
||||
async def synapse_reg_token(token):
|
||||
if request.method == "DELETE":
|
||||
return jsonify({})
|
||||
|
||||
return jsonify({
|
||||
"token": "Vona",
|
||||
"uses_allowed": config.the_funny_number,
|
||||
"pending": 0,
|
||||
"completed": 1,
|
||||
"expiry_time": None
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/registration_tokens/new", methods=["POST"])
|
||||
async def synapse_new_reg_token():
|
||||
return jsonify({
|
||||
"token": "Vona",
|
||||
"uses_allowed": config.the_funny_number,
|
||||
"pending": 0,
|
||||
"completed": 1,
|
||||
"expiry_time": None
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms")
|
||||
async def synapse_rooms():
|
||||
return jsonify({
|
||||
"rooms": [],
|
||||
"offset": 0,
|
||||
"total_rooms": 0
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>/members")
|
||||
async def synapse_room_members(room):
|
||||
return jsonify({
|
||||
"members": [
|
||||
f"@vona:{config.server_name}"
|
||||
],
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>/state")
|
||||
async def synapse_room_state(room):
|
||||
return jsonify({"state": []})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>/state")
|
||||
async def synapse_room_messages(room):
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"end": "vona",
|
||||
"start": "vona"
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>/block", methods=["GET", "PUT"])
|
||||
async def synapse_block_room(room):
|
||||
return jsonify({"block": False})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id>", methods=["DELETE"])
|
||||
async def synapse_room_delete(room):
|
||||
return jsonify({
|
||||
"kicked_users": [
|
||||
f"@vona:{config.server_name}"
|
||||
],
|
||||
"failed_to_kick_users": [],
|
||||
"local_aliases": [],
|
||||
"new_room_id": f"!vona:{config.server_name}"
|
||||
})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/rooms/<room_id>", methods=["DELETE"])
|
||||
async def synapse_room_delete_v2(room):
|
||||
return jsonify({"delete_id": "vona"})
|
||||
|
||||
@custom.route("/_synapse/admin/v2/rooms/<room_id>/delete_status")
|
||||
async def synapse_room_delete_status(room):
|
||||
return jsonify({"results": []})
|
||||
|
||||
@custom.route("/_synapse/admin/v1/rooms/<room_id_or_alias>/forward_extremities", methods=["GET"])
|
||||
async def synapse_forward_extremities(room):
|
||||
if request.method == "DELETE":
|
||||
return jsonify({"deleted": 0})
|
||||
|
||||
return jsonify({
|
||||
"count": 1,
|
||||
"results": [{
|
||||
"event_id": globals.make_event_id(),
|
||||
"state_group": config.the_funny_number,
|
||||
"depth": config.the_funny_number,
|
||||
"received_ts": config.the_funny_number
|
||||
}]
|
||||
})
|
||||
|
||||
# Dendrite - https://element-hq.github.io/dendrite/administration/adminapi
|
||||
@custom.route("/_dendrite/admin/evacuateUser/<userId>", methods=["POST"])
|
||||
async def dendrite_evacuate_user(userId):
|
||||
return jsonify({"affected": [globals.make_event_id().replace("$", "!")]})
|
||||
|
||||
@custom.route("/_dendrite/admin/evacuateRoom/<roomId>", methods=["POST"])
|
||||
async def dendrite_evacuate_room(roomId):
|
||||
return jsonify({"affected": [f"@vona:{config.server_name}"]})
|
||||
|
||||
@custom.route("/_dendrite/admin/resetPassword/<userId>", methods=["POST"])
|
||||
async def dendrite_reset_pswd(userId):
|
||||
return jsonify({"password_updated": True})
|
||||
|
||||
# Conduwuit/Tuwunel/Continuwuity
|
||||
@custom.route("/_continuwuity/local_user_count")
|
||||
@custom.route("/_conduwuit/local_user_count")
|
||||
@custom.route("/_tuwunel/local_user_count")
|
||||
async def conduwuit_user_count():
|
||||
return jsonify({"count": 1})
|
||||
|
||||
@custom.route("/_continuwuity/server_version")
|
||||
@custom.route("/_conduwuit/server_version")
|
||||
@custom.route("/_tuwunel/server_version")
|
||||
async def conduwuit_server_version():
|
||||
return jsonify({
|
||||
"name": "Vona",
|
||||
"version":globals.vona_version
|
||||
})
|
||||
485
vona/federation/__init__.py
Normal file
485
vona/federation/__init__.py
Normal file
@@ -0,0 +1,485 @@
|
||||
from flask import jsonify, Response, request, send_file, abort, Blueprint
|
||||
from vona.config import *
|
||||
import vona.globals as globals
|
||||
import httpx
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
|
||||
server = Blueprint("federation", __name__)
|
||||
|
||||
|
||||
def send_join(request, roomId) -> dict:
|
||||
event_chain = []
|
||||
event_hashes = []
|
||||
|
||||
event_ids = [
|
||||
globals.make_event_id(seed=f"1_{roomId}"),
|
||||
globals.make_event_id(seed=f"2_{roomId}"),
|
||||
globals.make_event_id(seed=f"3_{roomId}"),
|
||||
globals.make_event_id(seed=f"4_{roomId}"),
|
||||
globals.make_event_id(seed=f"5_{roomId}"),
|
||||
globals.make_event_id(seed=f"6_{roomId}"),
|
||||
]
|
||||
|
||||
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()
|
||||
|
||||
response = {
|
||||
"auth_chain": event_chain,
|
||||
"event": remote_join,
|
||||
"members_omitted": False,
|
||||
"servers_in_room": [server_name],
|
||||
"state": event_chain
|
||||
}
|
||||
|
||||
return response
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/version")
|
||||
async def version():
|
||||
return jsonify({
|
||||
"server": {
|
||||
"version": globals.vona_version,
|
||||
"name": "Vona"
|
||||
}
|
||||
})
|
||||
|
||||
@server.route("/_matrix/key/v2/server")
|
||||
async def keys():
|
||||
return jsonify(globals.sign_json({
|
||||
"old_verify_keys": {},
|
||||
"server_name": server_name,
|
||||
"valid_until_ts": int(time.time() * 1000 + 604800000),
|
||||
"verify_keys": {
|
||||
f"ed25519:{signing_key.split()[1]}": {
|
||||
"key": globals.pubkey()
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
@server.route("/_matrix/federation/v1/query/directory")
|
||||
async def room_query():
|
||||
return jsonify({
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"servers": [server_name]
|
||||
})
|
||||
|
||||
@server.route("/_matrix/federation/v1/media/download/<media_id>")
|
||||
async def download_media(media_id):
|
||||
# Auth media requires this to be
|
||||
# multipart despite not even using
|
||||
# it for anything. Minor annoyance.
|
||||
|
||||
with open(cat, "rb") as img_file:
|
||||
image_data = img_file.read()
|
||||
boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
|
||||
response_body = (
|
||||
f"--{boundary}\r\n"
|
||||
f"Content-Type: application/json\r\n\r\n"
|
||||
f"{{}}\r\n"
|
||||
f"--{boundary}\r\n"
|
||||
f"Content-Type: image/jpeg\r\n"
|
||||
f'Content-Disposition: attachment; filename="cat.jpg"\r\n\r\n'
|
||||
).encode() + image_data + f"\r\n--{boundary}--\r\n".encode()
|
||||
|
||||
response = Response(response_body, content_type=f"multipart/mixed; boundary={boundary}")
|
||||
response.status_code = 200
|
||||
|
||||
return response
|
||||
|
||||
@server.route("/_matrix/federation/v1/media/thumbnail/<media_id>")
|
||||
async def thumbnail_media(media_id):
|
||||
return jsonify({
|
||||
"errcode": "M_TOO_CUTE",
|
||||
"error": "Cat is too cute to thumbnail"
|
||||
}), 418
|
||||
|
||||
@server.route("/_matrix/federation/v1/send_join/<roomId>/<eventId>", methods=["PUT"])
|
||||
async def send_join_v1(roomId, eventId):
|
||||
return jsonify([200, send_join(request, roomId)])
|
||||
|
||||
@server.route("/_matrix/federation/v2/send_join/<roomId>/<eventId>", methods=["PUT"])
|
||||
async def send_join_v2(roomId, eventId):
|
||||
return jsonify(send_join(request, roomId))
|
||||
|
||||
@server.route("/_matrix/federation/v1/make_join/<roomId>/<userId>")
|
||||
async def make_join(roomId, userId):
|
||||
def not_invited():
|
||||
return jsonify({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "You are not invited to this room."
|
||||
}), 403
|
||||
|
||||
try:
|
||||
if roomId.split(":")[1] != server_name:
|
||||
return not_invited()
|
||||
except:
|
||||
return not_invited()
|
||||
|
||||
class bullshit:
|
||||
def get_json():
|
||||
return {}
|
||||
|
||||
state = send_join(
|
||||
request=bullshit,
|
||||
roomId=roomId
|
||||
)["state"]
|
||||
|
||||
join = {
|
||||
"content": {
|
||||
"join_authorised_via_users_server": f"@vona:{server_name}",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin": server_name,
|
||||
"origin_server_ts": 7,
|
||||
"room_id": roomId,
|
||||
"sender": userId,
|
||||
"state_key": userId,
|
||||
"type": "m.room.member",
|
||||
"depth": 7
|
||||
}
|
||||
|
||||
join["auth_events"] = [
|
||||
[
|
||||
state[0]["event_id"],
|
||||
state[0]["hashes"]
|
||||
],
|
||||
[
|
||||
state[2]["event_id"],
|
||||
state[2]["hashes"]
|
||||
],
|
||||
[
|
||||
state[3]["event_id"],
|
||||
state[3]["hashes"]
|
||||
]
|
||||
]
|
||||
|
||||
join["prev_events"] = [[
|
||||
state[5]["event_id"],
|
||||
state[5]["hashes"]
|
||||
]]
|
||||
|
||||
return jsonify({
|
||||
"event": globals.hash_and_sign_event(join),
|
||||
"room_version": "2"
|
||||
})
|
||||
|
||||
@server.route("/_matrix/federation/v1/publicRooms", methods=["POST", "GET"])
|
||||
async def room_directory():
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"total_room_count_estimate": 0
|
||||
})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/server-server-api/#transactions
|
||||
@server.route("/_matrix/federation/v1/send/<txnId>", methods=["PUT"])
|
||||
async def receive_txn(txnId):
|
||||
# We will need to implement a way to store every
|
||||
# event we need if we want to send events in the
|
||||
# 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/latest/rooms/v2/#authorization-rules
|
||||
|
||||
|
||||
data = request.data.decode("utf-8")
|
||||
parsed_data = json.loads(data)
|
||||
response = {"pdus": {}}
|
||||
|
||||
if "pdus" in parsed_data:
|
||||
for pdu in parsed_data["pdus"]:
|
||||
if "event_id" in pdu:
|
||||
response["pdus"][pdu["event_id"]] = {}
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/query/profile")
|
||||
async def user_profile():
|
||||
field = request.args.get("field")
|
||||
if field:
|
||||
if field == "avatar_url":
|
||||
return jsonify({"avatar_url":f"mxc://{server_name}/cat"})
|
||||
elif field == "displayname":
|
||||
return jsonify({"displayname":"Vona"})
|
||||
|
||||
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"})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/server-server-api/#device-management
|
||||
@server.route(f"/_matrix/federation/v1/user/devices/@/:{server_name}")
|
||||
@server.route("/_matrix/federation/v1/user/devices/<user>")
|
||||
async def user_devices(user):
|
||||
return jsonify({
|
||||
"devices": [],
|
||||
"stream_id": the_funny_number,
|
||||
"user_id": f"@vona:{server_name}"
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/user/keys/query", methods=["POST"])
|
||||
async def user_keys():
|
||||
try:
|
||||
users = request.json["device_keys"]
|
||||
except:
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "User does not exist"
|
||||
}), 404
|
||||
|
||||
return jsonify({"device_keys": users})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v2/invite/<room>/<txnId>", methods=["PUT"])
|
||||
async def invite_user_v2(room, txnId):
|
||||
return invite_user(request.data)
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/invite/<room>/<txnId>", methods=["PUT"])
|
||||
async def invite_user_v1(room, txnId):
|
||||
return [200, invite_user(request.data)]
|
||||
|
||||
|
||||
def invite_user(data):
|
||||
try:
|
||||
invite_data = json.loads(data)
|
||||
except:
|
||||
return jsonify({"errcode":"M_NOT_JSON","error":"Content not JSON."}),
|
||||
|
||||
if "event" in invite_data:
|
||||
if "room_version" in invite_data:
|
||||
if invite_data["room_version"] != "2":
|
||||
return jsonify({
|
||||
"errcode": "M_INCOMPATIBLE_ROOM_VERSION",
|
||||
"error": "Vona only supports room version 2.",
|
||||
"room_version": invite_data["room_version"]
|
||||
}), 400
|
||||
|
||||
event = invite_data.get("event", {})
|
||||
content = event.get("content", {})
|
||||
|
||||
# NOTE to crispycat: I know you loooooove this syntax
|
||||
if (
|
||||
"content" in event
|
||||
and "membership" in content
|
||||
and "state_key" in event
|
||||
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({
|
||||
"errcode": "M_FORBIDDEN",
|
||||
"error": "Invalid invitation PDU"
|
||||
}), 403
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/hierarchy/<roomId>")
|
||||
async def space_hierachy(roomId):
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Room does not exist."
|
||||
})
|
||||
226
vona/globals/__init__.py
Normal file
226
vona/globals/__init__.py
Normal file
@@ -0,0 +1,226 @@
|
||||
from importlib.metadata import version
|
||||
import vona.config as config
|
||||
import nacl.signing
|
||||
import hashlib
|
||||
import base64
|
||||
import random
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
|
||||
vona_version = version("vona")
|
||||
|
||||
|
||||
def canonical_json(value):
|
||||
return json.dumps(
|
||||
value,
|
||||
ensure_ascii=False,
|
||||
separators=(",", ":"),
|
||||
sort_keys=True,
|
||||
).encode("UTF-8")
|
||||
|
||||
|
||||
def sign_json(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)
|
||||
|
||||
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"}
|
||||
for key in unsigned_keys:
|
||||
del data[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("=")
|
||||
|
||||
new_signature = {f"ed25519:{key_version}": signature_base64}
|
||||
|
||||
if "signatures" in data:
|
||||
data["signatures"][config.server_name] = {
|
||||
**data["signatures"].get(config.server_name, {}),
|
||||
**new_signature,
|
||||
}
|
||||
else:
|
||||
data["signatures"] = {config.server_name: new_signature}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def make_event_id(seed=None):
|
||||
if seed is not None:
|
||||
random.seed(seed)
|
||||
|
||||
random_bytes = bytearray(random.getrandbits(8) for _ in range(32))
|
||||
|
||||
event_id = "$"
|
||||
event_id += re.sub(
|
||||
r"[\/+=]",
|
||||
"_",
|
||||
base64.b64encode(random_bytes).decode("utf-8"),
|
||||
).rstrip("=")[:44]
|
||||
|
||||
event_id += ":" + config.server_name
|
||||
|
||||
return event_id
|
||||
|
||||
|
||||
def event_hash(event_object):
|
||||
event_object = dict(event_object)
|
||||
|
||||
event_object.pop("unsigned", None)
|
||||
event_object.pop("signatures", None)
|
||||
event_object.pop("hashes", None)
|
||||
|
||||
event_json_bytes = canonical_json(event_object)
|
||||
|
||||
return base64.b64encode(hashlib.sha256(event_json_bytes).digest()).decode("utf-8")
|
||||
|
||||
|
||||
def pubkey() -> str:
|
||||
private_key = config.signing_key.split()[2]
|
||||
|
||||
while len(private_key) % 4 != 0:
|
||||
private_key += "="
|
||||
|
||||
public_key = nacl.signing.SigningKey(base64.b64decode(private_key)).verify_key
|
||||
|
||||
return (
|
||||
public_key.encode(encoder=nacl.encoding.Base64Encoder)
|
||||
.decode("utf-8")
|
||||
.rstrip("=")
|
||||
)
|
||||
|
||||
|
||||
def make_auth_header(destination, method, path, content=None) -> str:
|
||||
request_json = {
|
||||
"method": method,
|
||||
"uri": path,
|
||||
"origin": config.server_name,
|
||||
"destination": destination,
|
||||
}
|
||||
|
||||
if content is not None:
|
||||
request_json["content"] = content
|
||||
|
||||
signed_json = sign_json(request_json)
|
||||
|
||||
authorization_headers = []
|
||||
|
||||
for key, sig in signed_json["signatures"][config.server_name].items():
|
||||
authorization_headers.append(
|
||||
bytes(
|
||||
'X-Matrix origin="%s",destination="%s",key="%s",sig="%s"'
|
||||
% (
|
||||
config.server_name,
|
||||
destination,
|
||||
key,
|
||||
sig,
|
||||
),
|
||||
"utf-8",
|
||||
)
|
||||
)
|
||||
|
||||
return authorization_headers[0].decode("utf-8")
|
||||
|
||||
|
||||
def redact_event(event):
|
||||
# Returns a redacted event as per
|
||||
# the algorithm for v1/v2 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
|
||||
154
vona/identity/__init__.py
Normal file
154
vona/identity/__init__.py
Normal file
@@ -0,0 +1,154 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
from vona.config import server_name, the_funny_number
|
||||
import time
|
||||
|
||||
identity = Blueprint("identity", __name__)
|
||||
|
||||
# This implements being an identity server.
|
||||
# I'm pretty sure only Element uses this,
|
||||
# but oh well.
|
||||
|
||||
# https://spec.matrix.org/latest/identity-service-api/#api-version-check
|
||||
@identity.route("/_matrix/identity/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"
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/identity-service-api/#authentication
|
||||
@identity.route("/_matrix/identity/v2/account")
|
||||
async def account_info():
|
||||
return jsonify({"user_id": f"@vona:{server_name}"})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2/account/logout", methods=["POST"])
|
||||
async def logout():
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@identity.route("/_matrix/identity/v2/account/register", methods=["POST"])
|
||||
async def register():
|
||||
return jsonify({"token":"vona"})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/identity-service-api/#terms-of-service
|
||||
@identity.route("/_matrix/identity/v2/terms", methods=["GET", "POST"])
|
||||
async def policies():
|
||||
if request.method == "GET":
|
||||
return jsonify({"policies":{}})
|
||||
|
||||
return jsonify({})
|
||||
|
||||
@identity.route("/_matrix/identity/v2")
|
||||
async def status():
|
||||
return jsonify({})
|
||||
|
||||
@identity.route("/_matrix/identity/v2/pubkey/ephemeral/isvalid")
|
||||
@identity.route("/_matrix/identity/v2/pubkey/isvalid")
|
||||
async def pubkey_validity():
|
||||
return jsonify({"valid": True})
|
||||
|
||||
@identity.route("/_matrix/identity/v2/pubkey/<key>")
|
||||
async def get_key(key):
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "The public key was not found"
|
||||
}), 404
|
||||
|
||||
@identity.route("/_matrix/identity/v2/hash_details")
|
||||
async def hash_details():
|
||||
return jsonify({"algorithms":["none","sha256"],"lookup_pepper": "vona"})
|
||||
|
||||
@identity.route("/_matrix/identity/v2/lookup", methods=["POST"])
|
||||
async def lookup():
|
||||
req = request.json
|
||||
|
||||
if "addresses" in req:
|
||||
return jsonify({"mappings": {req["addresses"][0]: f"@vona:{server_name}"}})
|
||||
else:
|
||||
return jsonify({"errcode": "M_INVALID_PEPPER","error": "Invalid pepper"})
|
||||
|
||||
@identity.route("/_matrix/identity/v2/validate/email/requestToken", methods=["POST"])
|
||||
@identity.route("/_matrix/identity/v2/validate/msisdn/requestToken", methods=["POST"])
|
||||
async def request_validation_token():
|
||||
return jsonify({"sid": str(the_funny_number)})
|
||||
|
||||
@identity.route("/_matrix/identity/v2/validate/email/submitToken", methods=["GET", "POST"])
|
||||
@identity.route("/_matrix/identity/v2/validate/msisdn/submitToken", methods=["GET", "POST"])
|
||||
async def submit_validation_token():
|
||||
return jsonify({"success": True})
|
||||
|
||||
@identity.route("/_matrix/identity/v2/3pid/bind", methods=["POST"])
|
||||
async def threepid_bind():
|
||||
if "mxid" in request.get_json():
|
||||
mxid = request.get_json()["mxid"]
|
||||
else:
|
||||
mxid = f"@vona:{server_name}"
|
||||
|
||||
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"])
|
||||
async def threepid_unbind():
|
||||
return jsonify({})
|
||||
|
||||
@identity.route("/_matrix/identity/v2/3pid/getValidated3pid")
|
||||
async def threepid_validated():
|
||||
# Please email abuse@matrix.org
|
||||
return jsonify({
|
||||
"address": "abuse@matrix.org",
|
||||
"medium": "email",
|
||||
"validated_at": the_funny_number
|
||||
})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/identity-service-api/#invitation-storage
|
||||
@identity.route("/_matrix/identity/v2/store-invite", methods=["POST"])
|
||||
async def invite():
|
||||
return jsonify({
|
||||
"display_name": "Vona",
|
||||
"public_keys": [
|
||||
{
|
||||
"key_validity_url": f"https://{server_name}/_matrix/identity/v2/pubkey/isvalid",
|
||||
"public_key":"ohyeah"
|
||||
},
|
||||
{
|
||||
"key_validity_url": f"https://{server_name}/_matrix/identity/v2/pubkey/ephemeral/isvalid",
|
||||
"public_key":"thisssssss"
|
||||
}
|
||||
],
|
||||
"token": "vona"
|
||||
})
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/identity-service-api/#ephemeral-invitation-signing
|
||||
@identity.route("/_matrix/identity/v2/sign-ed25519", methods=["POST"])
|
||||
async def invite_signing():
|
||||
required_keys = {"mxid", "private_key", "token"}
|
||||
d = data.get_json()
|
||||
|
||||
if set(d.keys()) == required_keys:
|
||||
return jsonify(sign_json(d))
|
||||
else:
|
||||
return jsonify({
|
||||
"errcode": "M_UNRECOGNIZED",
|
||||
"error": "Didn't recognize token"
|
||||
}), 404
|
||||
28
vona/policy/__init__.py
Normal file
28
vona/policy/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from flask import jsonify, Blueprint, request
|
||||
|
||||
policy = Blueprint("policy", __name__)
|
||||
|
||||
matrix_org = [
|
||||
"dendrite.matrix.org",
|
||||
"beta.matrix.org",
|
||||
"matrix.org",
|
||||
"element.io",
|
||||
"t2bot.io",
|
||||
"t2l.io"
|
||||
]
|
||||
|
||||
|
||||
@policy.route("/_matrix/policy/unstable/org.matrix.msc4284/event/<eventId>/check", methods=["POST"])
|
||||
@policy.route("/_matrix/policy/v1/event/<eventId>/check", methods=["POST"])
|
||||
async def check_event(eventId):
|
||||
if request.get_json()["origin"] in matrix_org:
|
||||
return jsonify({"recommendation": "spam"})
|
||||
|
||||
return jsonify({"recommendation": "ok"})
|
||||
|
||||
|
||||
@policy.route("/_matrix/policy/unstable/org.matrix.msc4284/sign", methods=["POST"])
|
||||
@policy.route("/_matrix/policy/v1/sign", methods=["POST"])
|
||||
async def sign_event():
|
||||
# NOTE: trolled
|
||||
return jsonify({})
|
||||
9
vona/utils/makekey.py
Normal file
9
vona/utils/makekey.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# Generates a key in the format compatible with Synapse and Vona.
|
||||
|
||||
import base64
|
||||
import os
|
||||
|
||||
key = base64.b64encode(os.urandom(32)).decode("utf-8")[:43].replace("/", "_")
|
||||
key_id = base64.b64encode(os.urandom(32)).decode("utf-8")[:6].replace("/", "_")
|
||||
|
||||
print(f"ed25519 {key_id} {key}")
|
||||
Reference in New Issue
Block a user