Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
492ffc62e9
|
|||
|
9ca4c913f3
|
|||
|
6dd4fb04e3
|
|||
|
f771130a16
|
|||
|
9aa2e062e5
|
|||
|
c8d6f57e5b
|
|||
|
753625ad5c
|
|||
|
74a36e0b34
|
|||
|
b178011ad9
|
|||
|
76f89beb29
|
|||
|
7e8eb6de7f
|
|||
|
4e28507dea
|
|||
|
357d3b7429
|
|||
|
8a2bae706e
|
|||
|
4603ed86ac
|
|||
|
4a6e65226e
|
|||
|
fc9787b8ec
|
|||
|
2dbec63ff7
|
|||
|
2021fc027b
|
|||
|
e0115fe8db
|
3
SECURITY.md
Normal file
3
SECURITY.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# Security
|
||||
If you believe you have found a vulnerability in Vona, please email vel@riseup.net or DM `@vel:faelix.im` on Matrix.
|
||||
Do not disclose the details of the vulnerability until a public fix has been made.
|
||||
@@ -8,6 +8,7 @@ dependencies = [
|
||||
"httpx (>=0.28.1,<0.29.0)",
|
||||
"pynacl (>=1.6.0,<2.0.0)",
|
||||
"flask[async] (>=3.1.2,<4.0.0)",
|
||||
"resolvematrix @ git+https://codeberg.org/timedout/resolvematrix.git",
|
||||
]
|
||||
|
||||
authors = [
|
||||
|
||||
@@ -2,7 +2,9 @@ from flask import Flask, jsonify, request, redirect
|
||||
import vona.globals as globals
|
||||
from datetime import datetime
|
||||
import vona.config as config
|
||||
import threading
|
||||
import logging
|
||||
import os
|
||||
|
||||
from vona.federation import server
|
||||
from vona.custom import custom
|
||||
@@ -24,9 +26,20 @@ app.register_blueprint(server)
|
||||
app.register_blueprint(apps)
|
||||
|
||||
@app.before_request
|
||||
async def preflight():
|
||||
async def validate_json():
|
||||
if request.method == "OPTIONS":
|
||||
return "", 204
|
||||
return "", 200
|
||||
|
||||
elif request.method in ["PUT", "POST", "PATCH"]:
|
||||
if "media" in request.path:
|
||||
# Don't check media uploads
|
||||
return
|
||||
|
||||
try:
|
||||
request.get_json(force=True)
|
||||
except Exception as e:
|
||||
return jsonify({"error": "Content not JSON.", "errcode": "M_NOT_JSON"}), 400
|
||||
|
||||
|
||||
@app.after_request
|
||||
async def handle_logging(response):
|
||||
@@ -36,7 +49,6 @@ async def handle_logging(response):
|
||||
response.headers["Access-Control-Allow-Methods"] = "GET, HEAD, POST, PUT, DELETE, OPTIONS"
|
||||
|
||||
if request.method == "OPTIONS":
|
||||
# Discard logs for OPTIONS
|
||||
return response
|
||||
|
||||
origin = "unknown"
|
||||
@@ -44,13 +56,31 @@ async def handle_logging(response):
|
||||
try:
|
||||
if "Authorization" in request.headers:
|
||||
if request.headers["Authorization"].split()[0] == "X-Matrix":
|
||||
origin = request.headers["Authorization"].split('origin="')[1].split('"')[0]
|
||||
origin = (
|
||||
request.headers["Authorization"]
|
||||
.split("origin=")[1]
|
||||
.split(",")[0]
|
||||
)
|
||||
|
||||
while '"' in origin:
|
||||
origin = origin.replace('"', "")
|
||||
|
||||
if origin == config.server_name:
|
||||
return response
|
||||
|
||||
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}')
|
||||
print(
|
||||
f"[{origin}] " +
|
||||
f'[{datetime.now().strftime("%d/%b/%Y:%H:%M:%S")}] ' +
|
||||
request.method + " " +
|
||||
request.full_path.rstrip("?") + " " +
|
||||
str(response.status_code)
|
||||
)
|
||||
|
||||
return response
|
||||
|
||||
@@ -100,4 +130,20 @@ async def client():
|
||||
})
|
||||
|
||||
|
||||
def federation_self_test():
|
||||
try:
|
||||
resp = globals.http_client().get(
|
||||
path="/",
|
||||
destination=config.server_name,
|
||||
)
|
||||
|
||||
resp.raise_for_status()
|
||||
print("[INFO] Federation self-test OK")
|
||||
except Exception as e:
|
||||
print(f"[FATL] Federation self-test failed: {e}")
|
||||
os._exit(1)
|
||||
|
||||
threading.Thread(target=federation_self_test).start()
|
||||
|
||||
|
||||
app.run(host=config.addr, port=config.port)
|
||||
|
||||
@@ -56,7 +56,7 @@ async def spec_versions():
|
||||
@client.route("/_matrix/client/v3/admin/whois/<user>")
|
||||
@client.route("/_matrix/client/r0/admin/whois/<user>")
|
||||
async def whois(user):
|
||||
if userId.startswith("@"):
|
||||
if user.startswith("@"):
|
||||
return jsonify({
|
||||
"devices": {
|
||||
"": {
|
||||
@@ -644,14 +644,10 @@ async def presence(user):
|
||||
if request.method == "PUT":
|
||||
return jsonify({})
|
||||
|
||||
return jsonify({
|
||||
"presence": "online"
|
||||
})
|
||||
return jsonify({"presence": "online"})
|
||||
|
||||
|
||||
@client.route("/_matrix/client/v3/publicRooms", methods=["GET", "POST"])
|
||||
@client.route("/_matrix/client/r0/publicRooms", methods=["GET", "POST"])
|
||||
async def room_directory():
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"total_room_count_estimate": 0
|
||||
})
|
||||
return jsonify(globals.room_dir)
|
||||
|
||||
@@ -12,7 +12,7 @@ server_name: str = ""
|
||||
signing_key: str = ""
|
||||
support: dict = {"contacts": []}
|
||||
|
||||
_CONFIG_PATH = Path("/etc/vona/config.toml")
|
||||
_CONFIG_PATH = Path(os.getenv("VONA_CONFIG", "/etc/vona/config.toml"))
|
||||
|
||||
|
||||
def _fatal(msg: str) -> None:
|
||||
|
||||
@@ -2,16 +2,21 @@ from flask import Blueprint
|
||||
|
||||
custom = Blueprint("custom", __name__)
|
||||
|
||||
# This implements custom endpoints
|
||||
# used by other homeserver
|
||||
# implementations. They do not start
|
||||
# with /_matrix/
|
||||
# This implements non-standard
|
||||
# endpoints created by other
|
||||
# homeserver implementations.
|
||||
|
||||
|
||||
from .hammerhead import hammerhead
|
||||
from .conduwuit import conduwuit
|
||||
from .dendrite import dendrite
|
||||
from .telodendria import telo
|
||||
from .synapse import synapse
|
||||
from .citadel import citadel
|
||||
|
||||
custom.register_blueprint(hammerhead)
|
||||
custom.register_blueprint(conduwuit)
|
||||
custom.register_blueprint(dendrite)
|
||||
custom.register_blueprint(synapse)
|
||||
custom.register_blueprint(citadel)
|
||||
custom.register_blueprint(telo)
|
||||
|
||||
61
vona/custom/citadel.py
Normal file
61
vona/custom/citadel.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from flask import Blueprint, jsonify, request
|
||||
import vona.config as config
|
||||
import base64
|
||||
import os
|
||||
|
||||
citadel = Blueprint("citadel", __name__)
|
||||
|
||||
# These are endpoints made by Thales Citadel
|
||||
|
||||
# TODO: Add more endpoints, this likely
|
||||
# isn't all of them
|
||||
|
||||
|
||||
@citadel.route("/_matrix/client/r0/citadel/stats/m.news/<event>", methods=["GET", "PUT"])
|
||||
async def news_stats(event):
|
||||
if request.method == "PUT":
|
||||
return jsonify({"success": True})
|
||||
|
||||
return jsonify({
|
||||
"total_clicks": config.the_funny_number,
|
||||
"user_readings": config.the_funny_number
|
||||
})
|
||||
|
||||
|
||||
@citadel.route("/_matrix/client/r0/citadel/rooms/<room>/closeRoom", methods=["POST"])
|
||||
async def close_room(room):
|
||||
store_response = request.json.get("store_response", True)
|
||||
|
||||
operation_id = base64.b64encode(
|
||||
bytes(
|
||||
os.urandom(8).hex(),
|
||||
"utf-8"
|
||||
)
|
||||
).decode("utf-8")[:14]
|
||||
|
||||
|
||||
if store_response:
|
||||
resp = {"operation_id": operation_id}
|
||||
else:
|
||||
resp = {
|
||||
"operation_id": operation_id,
|
||||
"previous_state": {
|
||||
"progress": {
|
||||
"steps": {
|
||||
"step_kick_users": {
|
||||
"status": "complete"
|
||||
},
|
||||
"step_purge_history": {
|
||||
"status": "complete"
|
||||
},
|
||||
"step_purge_media": {
|
||||
"status": "complete"
|
||||
}
|
||||
}
|
||||
},
|
||||
"status": "complete"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return jsonify(resp)
|
||||
77
vona/custom/hammerhead.py
Normal file
77
vona/custom/hammerhead.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from vona.config import the_funny_number
|
||||
from datetime import datetime, timezone
|
||||
from vona.federation import send_join
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
import time
|
||||
|
||||
hammerhead = Blueprint("hammerhead", __name__)
|
||||
|
||||
# Hammerhead endpoints. Not documented, but code is at:
|
||||
# https://codeberg.org/timedout/hammerhead/src/branch/dev/hammerhead/router/routes/hammerhead
|
||||
|
||||
|
||||
@hammerhead.route("/_hammerhead/uptime")
|
||||
async def uptime():
|
||||
return jsonify({"started_at": config.the_funny_number})
|
||||
|
||||
@hammerhead.route("/_hammerhead/version")
|
||||
async def version():
|
||||
return jsonify({"version": globals.version})
|
||||
|
||||
|
||||
@hammerhead.route("/_hammerhead/admin/server-info/<server>")
|
||||
async def server_info(server):
|
||||
return jsonify({
|
||||
"destination": {
|
||||
"expires": datetime.now(timezone.utc).isoformat(),
|
||||
"host_header": server,
|
||||
"ip_port": [
|
||||
f"{server}:443"
|
||||
],
|
||||
"server_name": server
|
||||
},
|
||||
"keys": globals.sign_json({
|
||||
"old_verify_keys": {},
|
||||
"server_name": server,
|
||||
"valid_until_ts": int(time.time() * 1000 + 604800000),
|
||||
"verify_keys": {
|
||||
f"ed25519:{config.signing_key.split()[1]}": {
|
||||
"key": globals.pubkey()
|
||||
}
|
||||
}
|
||||
}),
|
||||
"software": {
|
||||
"server": {
|
||||
"version": globals.version,
|
||||
"name": "Vona"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@hammerhead.route("/_hammerhead/admin/rooms/<room>/state-resolver")
|
||||
async def room_state(room):
|
||||
class bullshit:
|
||||
def get_json():
|
||||
return {}
|
||||
|
||||
state = send_join(bullshit, room)["state"]
|
||||
formatted_state = {}
|
||||
event_cache = {}
|
||||
|
||||
for event in state:
|
||||
key = f"({event["type"]},'{event["state_key"]}')"
|
||||
formatted_state[key] = event
|
||||
|
||||
event_id = event["event_id"]
|
||||
event_cache[event_id] = event
|
||||
|
||||
|
||||
return jsonify({
|
||||
"current_state": formatted_state,
|
||||
"event_cache": event_cache,
|
||||
"room_id": room,
|
||||
"room_version": globals.room_version_from_id(room)
|
||||
})
|
||||
@@ -24,9 +24,9 @@ synapse = Blueprint("synapse", __name__)
|
||||
@synapse.route("/_synapse/admin/v1/media/unquarantine/<s>/<media_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/federation/destinations/<destination>/reset_connection", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>/timestamp_to_event")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/timestamp_to_event")
|
||||
@synapse.route("/_synapse/admin/v2/rooms/delete_status/<delete_id>")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id_or_alias>/make_room_admin", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/make_room_admin", methods=["POST"])
|
||||
async def response(**kwargs):
|
||||
return jsonify({})
|
||||
|
||||
@@ -118,9 +118,24 @@ async def account_data(user_id):
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/media", methods=["GET", "DELETE"])
|
||||
async def 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({
|
||||
"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": None,
|
||||
"safe_from_quarantine": False,
|
||||
"upload_name": "cat.jpg"
|
||||
}],
|
||||
"total": config.the_funny_number
|
||||
})
|
||||
|
||||
return jsonify({"deleted_media": ["cat"], "total": config.the_funny_number})
|
||||
return jsonify({
|
||||
"deleted_media": ["cat"],
|
||||
"total": config.the_funny_number
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/users/<user_id>/login", methods=["POST"])
|
||||
async def account_login(user_id):
|
||||
@@ -137,7 +152,7 @@ async def device_list(user_id):
|
||||
"devices": [{
|
||||
"device_id": "VVOONNAA",
|
||||
"display_name": "Vona",
|
||||
"last_seen_ip": "127.0.0.1",
|
||||
"last_seen_ip": config.addr,
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"last_seen_user_agent": f"Vona/{globals.version}"
|
||||
}],
|
||||
@@ -152,7 +167,7 @@ async def device_info(user_id, device_id):
|
||||
return jsonify({
|
||||
"device_id": "VVOONNAA",
|
||||
"display_name": "Vona",
|
||||
"last_seen_ip": "127.0.0.1",
|
||||
"last_seen_ip": config.addr,
|
||||
"last_seen_ts": config.the_funny_number,
|
||||
"last_seen_user_agent": f"Vona/{globals.version}"
|
||||
})
|
||||
@@ -169,11 +184,11 @@ async def username_available():
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/threepid/<medium>/users/<addr>")
|
||||
@synapse.route("/_synapse/admin/v1/auth_providers/<provider>/users/<ext>")
|
||||
async def threepid(p, a):
|
||||
async def threepid(**kwargs):
|
||||
return jsonify({"user_id": f"@vona:{config.server_name}"})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/<user_id>/redact")
|
||||
def redact(user_id):
|
||||
async def redact(user_id):
|
||||
return jsonify({"redact_id": os.urandom(16).hex()})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/user/redact_status/<redact_id>")
|
||||
@@ -191,8 +206,8 @@ async def register():
|
||||
|
||||
return jsonify({"access_token": "vona"})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/join/<roomId>", methods=["POST"])
|
||||
async def membership_manipulation(roomId):
|
||||
@synapse.route("/_synapse/admin/v1/join/<room>", methods=["POST"])
|
||||
async def membership_manipulation(room):
|
||||
return jsonify({"room_id": globals.make_event_id().replace("$", "!")})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/account_validity/validity", methods=["POST"])
|
||||
@@ -204,8 +219,8 @@ async def account_validity():
|
||||
async def server_notice(**kwargs):
|
||||
return jsonify({"event_id": globals.make_event_id()})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/purge_history/<room_id>/<event_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/purge_history/<room_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/purge_history/<room>/<event_id>", methods=["POST"])
|
||||
@synapse.route("/_synapse/admin/v1/purge_history/<room>", methods=["POST"])
|
||||
async def purge_event(**kwargs):
|
||||
return jsonify({"purge_id": os.urandom(16).hex()})
|
||||
|
||||
@@ -213,12 +228,12 @@ async def purge_event(**kwargs):
|
||||
async def purge_status(purge_id):
|
||||
return jsonify({"status":"active"})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/room/<room_id>/media")
|
||||
async def room_media(room_id):
|
||||
@synapse.route("/_synapse/admin/v1/room/<room>/media")
|
||||
async def room_media(room):
|
||||
return jsonify({"local": [f"mxc://{config.server_name}/cat"], "remote": []})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/room/<room_id>/media/quarantine", methods=["POST"])
|
||||
async def quarantine_room_media(room_id):
|
||||
@synapse.route("/_synapse/admin/v1/room/<room>/media/quarantine", methods=["POST"])
|
||||
async def quarantine_room_media(room):
|
||||
return jsonify({"num_quarantined": config.the_funny_number})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/media/<s>/delete", methods=["POST"])
|
||||
@@ -233,7 +248,15 @@ async def delete_remote_media():
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/statistics/users/media")
|
||||
async def 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})
|
||||
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
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/statistics/database/rooms")
|
||||
async def room_stats():
|
||||
@@ -302,7 +325,7 @@ async def interact_with_reported_event(report_id):
|
||||
@synapse.route("/_synapse/admin/v1/federation/destinations")
|
||||
async def federation_destinations():
|
||||
return jsonify({
|
||||
"destinations": [{}],
|
||||
"destinations": [],
|
||||
"total": 0
|
||||
})
|
||||
|
||||
@@ -366,7 +389,7 @@ async def rooms():
|
||||
"total_rooms": 0
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>/members")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/members")
|
||||
async def room_members(room):
|
||||
return jsonify({
|
||||
"members": [
|
||||
@@ -375,11 +398,11 @@ async def room_members(room):
|
||||
"total": 1
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>/state")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/state")
|
||||
async def room_state(room):
|
||||
return jsonify({"state": []})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>/state")
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/state")
|
||||
async def room_messages(room):
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
@@ -387,11 +410,11 @@ async def room_messages(room):
|
||||
"start": "vona"
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>/block", methods=["GET", "PUT"])
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/block", methods=["GET", "PUT"])
|
||||
async def block_room(room):
|
||||
return jsonify({"block": False})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id>", methods=["DELETE"])
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>", methods=["DELETE"])
|
||||
async def room_delete(room):
|
||||
return jsonify({
|
||||
"kicked_users": [
|
||||
@@ -399,18 +422,18 @@ async def room_delete(room):
|
||||
],
|
||||
"failed_to_kick_users": [],
|
||||
"local_aliases": [],
|
||||
"new_room_id": f"!vona:{config.server_name}"
|
||||
"new_room_id": globals.make_event_id(seed=room)
|
||||
})
|
||||
|
||||
@synapse.route("/_synapse/admin/v2/rooms/<room_id>", methods=["DELETE"])
|
||||
@synapse.route("/_synapse/admin/v2/rooms/<room>", methods=["DELETE"])
|
||||
async def room_delete_v2(room):
|
||||
return jsonify({"delete_id": "vona"})
|
||||
|
||||
@synapse.route("/_synapse/admin/v2/rooms/<room_id>/delete_status")
|
||||
@synapse.route("/_synapse/admin/v2/rooms/<room>/delete_status")
|
||||
async def room_delete_status(room):
|
||||
return jsonify({"results": []})
|
||||
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room_id_or_alias>/forward_extremities", methods=["GET"])
|
||||
@synapse.route("/_synapse/admin/v1/rooms/<room>/forward_extremities", methods=["GET"])
|
||||
async def forward_extremities(room):
|
||||
if request.method == "DELETE":
|
||||
return jsonify({"deleted": 0})
|
||||
|
||||
85
vona/custom/telodendria.py
Normal file
85
vona/custom/telodendria.py
Normal file
@@ -0,0 +1,85 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from vona.globals import version
|
||||
import vona.config as config
|
||||
|
||||
telo = Blueprint("telodendria", __name__)
|
||||
|
||||
# The telodendria admin API as specified by
|
||||
# https://git.telodendria.org/Telodendria/Telodendria/src/branch/master/docs/user/admin/README.md
|
||||
|
||||
|
||||
@telo.route("/_telodendria/admin/v1/restart", methods=["POST"])
|
||||
@telo.route("/_telodendria/admin/v1/shutdown", methods=["POST"])
|
||||
async def process_management(**kwargs):
|
||||
return jsonify({})
|
||||
|
||||
|
||||
@telo.route("/_telodendria/admin/v1/privileges/<lp>", methods=["GET", "PUT", "POST", "DELETE"])
|
||||
@telo.route("/_telodendria/admin/v1/privileges/", methods=["GET", "POST"])
|
||||
async def privileges(lp=None):
|
||||
return jsonify({
|
||||
"privileges": [
|
||||
"GRANT_PRIVILEGES",
|
||||
"PROC_CONTROL",
|
||||
"ISSUE_TOKENS",
|
||||
"DEACTIVATE",
|
||||
"CONFIG",
|
||||
"ALIAS",
|
||||
"ALL"
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@telo.route("/_telodendria/admin/v1/config", methods=["GET", "PUT", "POST"])
|
||||
async def configuration():
|
||||
if request.method == "GET":
|
||||
return jsonify({
|
||||
"listen": [
|
||||
{
|
||||
"tls": None,
|
||||
"port": config.port,
|
||||
"threads": 1,
|
||||
"maxConnections": 32
|
||||
}
|
||||
],
|
||||
"serverName": config.server_name,
|
||||
"pid": "/dev/null",
|
||||
"baseUrl": f"https://{config.server_name}/",
|
||||
"identityServer": f"https://{config.server_name}/",
|
||||
"runAs": None,
|
||||
"federation": True,
|
||||
"registration": config.users_can_register,
|
||||
"log": {
|
||||
"output": "stdout",
|
||||
"level": "message",
|
||||
"timestampFormat": "default",
|
||||
"color": True
|
||||
},
|
||||
"maxCache": config.the_funny_number
|
||||
})
|
||||
|
||||
return jsonify({"restart_required": True})
|
||||
|
||||
|
||||
@telo.route("/_telodendria/admin/v1/stats")
|
||||
async def stats():
|
||||
return jsonify({
|
||||
"memory_allocated": config.the_funny_number,
|
||||
"version": version
|
||||
})
|
||||
|
||||
|
||||
@telo.route("/_telodendria/admin/v1/tokens/<name>", methods=["GET", "DELETE"])
|
||||
@telo.route("/_telodendria/admin/v1/tokens", methods=["GET", "POST"])
|
||||
async def tokens(name=None):
|
||||
if request.method == "DELETE":
|
||||
return jsonify({})
|
||||
|
||||
return jsonify({
|
||||
"name": "vona",
|
||||
"created_by": "vona",
|
||||
"created_on": config.the_funny_number,
|
||||
"expires_on": config.the_funny_number,
|
||||
"used": config.the_funny_number,
|
||||
"uses": config.the_funny_number
|
||||
})
|
||||
@@ -1,7 +1,6 @@
|
||||
from flask import jsonify, Response, request, send_file, abort, Blueprint
|
||||
from vona.config import *
|
||||
import vona.globals as globals
|
||||
import httpx
|
||||
import vona.config as config
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
@@ -25,13 +24,13 @@ def send_join(request, roomId) -> dict:
|
||||
create_event = {
|
||||
"content": {
|
||||
"m.federate": True,
|
||||
"creator": f"@vona:{server_name}",
|
||||
"creator": f"@vona:{config.server_name}",
|
||||
"room_version": globals.room_version_from_id(roomId)
|
||||
},
|
||||
"event_id": event_ids[0],
|
||||
"origin_server_ts": 1,
|
||||
"room_id": roomId,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"depth": 1,
|
||||
"type": "m.room.create",
|
||||
@@ -45,12 +44,12 @@ def send_join(request, roomId) -> dict:
|
||||
our_join = {
|
||||
"content": {
|
||||
"displayname": "Vona",
|
||||
"avatar_url": f"mxc://{server_name}/cat",
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin_server_ts": 2,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"state_key": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": f"@vona:{config.server_name}",
|
||||
"type": "m.room.member",
|
||||
"event_id": event_ids[1],
|
||||
"room_id": roomId,
|
||||
@@ -71,17 +70,17 @@ def send_join(request, roomId) -> dict:
|
||||
pls = {
|
||||
"content": {
|
||||
"users": {
|
||||
f"@vona:{server_name}": "100"
|
||||
f"@vona:{config.server_name}": "100"
|
||||
}
|
||||
},
|
||||
"origin_server_ts": 3,
|
||||
"room_id": roomId,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.power_levels",
|
||||
"event_id": event_ids[2],
|
||||
"depth": 3,
|
||||
"user_id": f"@vona:{server_name}",
|
||||
"user_id": f"@vona:{config.server_name}",
|
||||
"auth_events": [
|
||||
[
|
||||
screate_event["event_id"],
|
||||
@@ -106,7 +105,7 @@ def send_join(request, roomId) -> dict:
|
||||
"join_rule": "public"
|
||||
},
|
||||
"origin_server_ts": 4,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.join_rules",
|
||||
"event_id": event_ids[3],
|
||||
@@ -141,7 +140,7 @@ def send_join(request, roomId) -> dict:
|
||||
},
|
||||
"origin_server_ts": 5,
|
||||
"depth": 5,
|
||||
"sender": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"type": "m.room.guest_access",
|
||||
"event_id": event_ids[4],
|
||||
@@ -158,10 +157,6 @@ def send_join(request, roomId) -> dict:
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
],
|
||||
[
|
||||
sjoin_rule["event_id"],
|
||||
sjoin_rule["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
@@ -178,7 +173,7 @@ def send_join(request, roomId) -> dict:
|
||||
"history_visibility": "shared"
|
||||
},
|
||||
"type": "m.room.history_visibility",
|
||||
"sender": f"@vona:{server_name}",
|
||||
"sender": f"@vona:{config.server_name}",
|
||||
"state_key": "",
|
||||
"origin_server_ts": 6,
|
||||
"depth": 6,
|
||||
@@ -196,10 +191,6 @@ def send_join(request, roomId) -> dict:
|
||||
[
|
||||
spls["event_id"],
|
||||
spls["hashes"]
|
||||
],
|
||||
[
|
||||
sjoin_rule["event_id"],
|
||||
sjoin_rule["hashes"]
|
||||
]
|
||||
],
|
||||
"prev_events": [[
|
||||
@@ -217,7 +208,7 @@ def send_join(request, roomId) -> dict:
|
||||
"auth_chain": event_chain,
|
||||
"event": remote_join,
|
||||
"members_omitted": False,
|
||||
"servers_in_room": [server_name],
|
||||
"servers_in_room": [config.server_name],
|
||||
"state": event_chain
|
||||
}
|
||||
|
||||
@@ -237,10 +228,10 @@ async def version():
|
||||
async def keys():
|
||||
return jsonify(globals.sign_json({
|
||||
"old_verify_keys": {},
|
||||
"server_name": server_name,
|
||||
"server_name": config.server_name,
|
||||
"valid_until_ts": int(time.time() * 1000 + 604800000),
|
||||
"verify_keys": {
|
||||
f"ed25519:{signing_key.split()[1]}": {
|
||||
f"ed25519:{config.signing_key.split()[1]}": {
|
||||
"key": globals.pubkey()
|
||||
}
|
||||
}
|
||||
@@ -250,7 +241,7 @@ async def keys():
|
||||
async def room_query():
|
||||
return jsonify({
|
||||
"room_id": globals.make_event_id().replace("$", "!"),
|
||||
"servers": [server_name]
|
||||
"servers": [config.server_name]
|
||||
})
|
||||
|
||||
@server.route("/_matrix/federation/v1/media/download/<media_id>")
|
||||
@@ -259,7 +250,7 @@ async def download_media(media_id):
|
||||
# multipart despite not even using
|
||||
# it for anything. Minor annoyance.
|
||||
|
||||
with open(cat, "rb") as img_file:
|
||||
with open(config.cat, "rb") as img_file:
|
||||
image_data = img_file.read()
|
||||
boundary = "----WebKitFormBoundary7MA4YWxkTrZu0gW"
|
||||
response_body = (
|
||||
@@ -300,7 +291,7 @@ async def make_join(roomId, userId):
|
||||
}), 403
|
||||
|
||||
try:
|
||||
if roomId.split(":")[1] != server_name:
|
||||
if roomId.split(":")[1] != config.server_name:
|
||||
return not_invited()
|
||||
except:
|
||||
return not_invited()
|
||||
@@ -316,10 +307,10 @@ async def make_join(roomId, userId):
|
||||
|
||||
join = {
|
||||
"content": {
|
||||
"join_authorised_via_users_server": f"@vona:{server_name}",
|
||||
"join_authorised_via_users_server": f"@vona:{config.server_name}",
|
||||
"membership": "join"
|
||||
},
|
||||
"origin": server_name,
|
||||
"origin": config.server_name,
|
||||
"origin_server_ts": 7,
|
||||
"room_id": roomId,
|
||||
"sender": userId,
|
||||
@@ -353,12 +344,10 @@ async def make_join(roomId, userId):
|
||||
"room_version": globals.room_version_from_id(roomId)
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/publicRooms", methods=["POST", "GET"])
|
||||
async def room_directory():
|
||||
return jsonify({
|
||||
"chunk": [],
|
||||
"total_room_count_estimate": 0
|
||||
})
|
||||
return jsonify(globals.room_dir)
|
||||
|
||||
|
||||
# https://spec.matrix.org/latest/server-server-api/#transactions
|
||||
@@ -395,7 +384,7 @@ async def user_profile():
|
||||
field = request.args.get("field")
|
||||
if field:
|
||||
if field == "avatar_url":
|
||||
return jsonify({"avatar_url":f"mxc://{server_name}/cat"})
|
||||
return jsonify({"avatar_url":f"mxc://{config.server_name}/cat"})
|
||||
elif field == "displayname":
|
||||
return jsonify({"displayname":"Vona"})
|
||||
|
||||
@@ -404,31 +393,24 @@ async def user_profile():
|
||||
"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://{config.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(f"/_matrix/federation/v1/user/devices/@/:{config.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}"
|
||||
"stream_id": config.the_funny_number,
|
||||
"user_id": f"@vona:{config.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})
|
||||
return jsonify({
|
||||
"device_keys": request.json.get("device_keys", {})
|
||||
})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v2/invite/<room>/<txnId>", methods=["PUT"])
|
||||
@@ -438,21 +420,16 @@ async def invite_user_v2(room, txnId):
|
||||
|
||||
@server.route("/_matrix/federation/v1/invite/<room>/<txnId>", methods=["PUT"])
|
||||
async def invite_user_v1(room, txnId):
|
||||
return [200, invite_user(request.data)]
|
||||
return [200, invite_user(request.json)]
|
||||
|
||||
|
||||
def invite_user(data):
|
||||
try:
|
||||
invite_data = json.loads(data)
|
||||
except:
|
||||
return jsonify({"errcode":"M_NOT_JSON","error":"Content not JSON."}),
|
||||
|
||||
def invite_user(invite_data):
|
||||
if "event" in invite_data:
|
||||
if "room_version" in invite_data:
|
||||
if invite_data["room_version"] != "2":
|
||||
if invite_data["room_version"] not in ["1", "2"]:
|
||||
return jsonify({
|
||||
"errcode": "M_INCOMPATIBLE_ROOM_VERSION",
|
||||
"error": "Vona only supports room version 2.",
|
||||
"error": "Unsupported room version",
|
||||
"room_version": invite_data["room_version"]
|
||||
}), 400
|
||||
|
||||
@@ -466,9 +443,12 @@ def invite_user(data):
|
||||
and "state_key" in event
|
||||
and "room_id" in event
|
||||
and content["membership"] == "invite"
|
||||
and event["state_key"] == f"@vona:{server_name}"
|
||||
and event["state_key"] == f"@vona:{config.server_name}"
|
||||
):
|
||||
return jsonify({"event": globals.sign_json_without_discard(invite_data["event"]), "room_version": "2"})
|
||||
return jsonify({
|
||||
"event": globals.sign_json_without_discard(invite_data["event"]),
|
||||
"room_version": invite_data["room_version"]
|
||||
})
|
||||
|
||||
|
||||
return jsonify({
|
||||
@@ -482,4 +462,26 @@ async def space_hierachy(roomId):
|
||||
return jsonify({
|
||||
"errcode": "M_NOT_FOUND",
|
||||
"error": "Room does not exist."
|
||||
}), 404
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/org.matrix.msc4358/discover_common_rooms", methods=["POST"])
|
||||
@server.route("/_matrix/federation/v1/discover_common_rooms", methods=["POST"])
|
||||
async def discover_common_rooms():
|
||||
tags = request.json.get("room_participation_tags", [])
|
||||
return jsonify({"recognised_tags": tags})
|
||||
|
||||
|
||||
@server.route("/_matrix/federation/v1/backfill/<room>")
|
||||
async def backfill(room):
|
||||
# TODO: burger king foot lettuce
|
||||
|
||||
class bullshit:
|
||||
def get_json():
|
||||
return {}
|
||||
|
||||
return jsonify({
|
||||
"origin": config.server_name,
|
||||
"origin_server_ts": int(str(time.time() * 1000).split(".")[0]),
|
||||
"pdus": send_join(bullshit, room)["state"]
|
||||
})
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
from resolvematrix import ServerResolver
|
||||
from types import SimpleNamespace
|
||||
from collections import Counter
|
||||
import vona.config as config
|
||||
import nacl.signing
|
||||
import hashlib
|
||||
import base64
|
||||
import random
|
||||
import httpx
|
||||
import copy
|
||||
import json
|
||||
import re
|
||||
|
||||
version = "1.4.2"
|
||||
version = "1.4.4"
|
||||
|
||||
|
||||
def canonical_json(value):
|
||||
@@ -109,7 +112,9 @@ def event_hash(event_object):
|
||||
|
||||
event_json_bytes = canonical_json(event_object)
|
||||
|
||||
return base64.b64encode(hashlib.sha256(event_json_bytes).digest()).decode("utf-8")
|
||||
return base64.b64encode(
|
||||
hashlib.sha256(event_json_bytes).digest()
|
||||
).decode("utf-8").rstrip("=")
|
||||
|
||||
|
||||
def pubkey() -> str:
|
||||
@@ -127,7 +132,12 @@ def pubkey() -> str:
|
||||
)
|
||||
|
||||
|
||||
def make_auth_header(destination, method, path, content=None) -> str:
|
||||
def make_auth_header(
|
||||
destination: str,
|
||||
method: str,
|
||||
path: str,
|
||||
content = None
|
||||
) -> str:
|
||||
request_json = {
|
||||
"method": method,
|
||||
"uri": path,
|
||||
@@ -250,3 +260,89 @@ def room_version_from_id(room_id):
|
||||
return most_common[0] if most_common else None
|
||||
|
||||
return most_common_character(nums)[0]
|
||||
|
||||
|
||||
room_dir = {
|
||||
"chunk": [{
|
||||
"avatar_url": f"mxc://{config.server_name}/cat",
|
||||
"guest_can_join": False,
|
||||
"join_rule": "public",
|
||||
"name": "Vona",
|
||||
"num_joined_members": 1,
|
||||
"room_id": make_event_id().replace("$", "!"),
|
||||
"room_type": "m.room",
|
||||
"topic": "",
|
||||
"world_readable": False,
|
||||
"via": [config.server_name]
|
||||
}],
|
||||
"total_room_count_estimate": 1
|
||||
}
|
||||
|
||||
|
||||
class http_client:
|
||||
http = httpx.Client(headers={"User-Agent": f"Vona/{version}"})
|
||||
resolver = ServerResolver(client=http)
|
||||
|
||||
def _resolve(self, target) -> SimpleNamespace:
|
||||
r = self.resolver.resolve(target)
|
||||
|
||||
if r.sni:
|
||||
sni = r.sni
|
||||
else:
|
||||
sni = r.host_header
|
||||
|
||||
return SimpleNamespace(
|
||||
base_url=r.base_url,
|
||||
host_header=r.host_header,
|
||||
sni=sni
|
||||
)
|
||||
|
||||
def put(
|
||||
self,
|
||||
path: str,
|
||||
destination: str,
|
||||
headers: dict = {},
|
||||
authorize: bool = True,
|
||||
json: dict = {},
|
||||
):
|
||||
resolved = self._resolve(destination)
|
||||
|
||||
if authorize:
|
||||
headers["Authorization"] = make_auth_header(
|
||||
method="PUT",
|
||||
destination=destination,
|
||||
path=path,
|
||||
)
|
||||
|
||||
headers["Host"] = resolved.host_header
|
||||
|
||||
return self.http.put(
|
||||
f"{resolved.base_url}{path}",
|
||||
headers=headers,
|
||||
extensions={"sni_hostname": resolved.sni},
|
||||
json=json
|
||||
)
|
||||
|
||||
def get(
|
||||
self,
|
||||
path: str,
|
||||
destination: str,
|
||||
headers: dict = {},
|
||||
authorize: bool = True,
|
||||
):
|
||||
resolved = self._resolve(destination)
|
||||
|
||||
if authorize:
|
||||
headers["Authorization"] = make_auth_header(
|
||||
method="GET",
|
||||
destination=destination,
|
||||
path=path,
|
||||
)
|
||||
|
||||
headers["Host"] = resolved.host_header
|
||||
|
||||
return self.http.get(
|
||||
f"{resolved.base_url}{path}",
|
||||
headers=headers,
|
||||
extensions={"sni_hostname": resolved.sni}
|
||||
)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
print("Available utils:")
|
||||
|
||||
a = [
|
||||
"makekey"
|
||||
"makekey",
|
||||
"joinroom"
|
||||
]
|
||||
|
||||
for t in a:
|
||||
|
||||
83
vona/utils/joinroom.py
Normal file
83
vona/utils/joinroom.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from resolvematrix import ServerResolver
|
||||
import urllib.parse, time, json, httpx
|
||||
import vona.globals as globals
|
||||
import vona.config as config
|
||||
|
||||
http_client = globals.http_client()
|
||||
|
||||
|
||||
def get_user_input(prompt):
|
||||
try:
|
||||
answer = input(prompt)
|
||||
while "\\n" in answer:
|
||||
answer = answer.replace("\\n", "\n")
|
||||
|
||||
return urllib.parse.quote(answer)
|
||||
except Exception as e:
|
||||
print(f"Error reading input: {e}")
|
||||
return None
|
||||
|
||||
|
||||
username = get_user_input("Username:\n\t")
|
||||
room_id = get_user_input("Room ID:\n\t")
|
||||
|
||||
|
||||
try:
|
||||
server_name = input("\nServer name to join via:\n\t")
|
||||
except Exception as e:
|
||||
print(f"Error reading server names: {e}")
|
||||
exit(1)
|
||||
|
||||
resolver = ServerResolver(client=http_client)
|
||||
|
||||
try:
|
||||
print("\nSending make_join request..")
|
||||
|
||||
make_join_response = http_client.get(
|
||||
path=f"/_matrix/federation/v1/make_join/{room_id}/%40{username}%3A{config.server_name}?ver=1&ver=2&ver=3&ver=4&ver=5&ver=6&ver=7&ver=8&ver=9&ver=10&ver=11&ver=12",
|
||||
destination=server_name,
|
||||
)
|
||||
|
||||
make_join_response.raise_for_status()
|
||||
make_join = make_join_response.json()
|
||||
|
||||
except httpx.HTTPStatusError as e:
|
||||
print(f"HTTP error occurred: {e.response.status_code} - {e.response.text}")
|
||||
exit(1)
|
||||
except json.JSONDecodeError:
|
||||
print("Failed to decode response.")
|
||||
exit(1)
|
||||
except Exception as e:
|
||||
print(f"An error occurred: {e}")
|
||||
exit(1)
|
||||
|
||||
|
||||
join_event = make_join.get("event", {})
|
||||
if make_join.get("room_version", "1") in ["1", "2"]:
|
||||
# NOTE: if we always make it opaque than Synapse will 500 lmao
|
||||
join_event["event_id"] = globals.make_event_id()
|
||||
|
||||
timestamp = input("\nTimestamp (leave blank for now):\n\t")
|
||||
try:
|
||||
join_event["origin_server_ts"] = int(timestamp)
|
||||
except ValueError:
|
||||
join_event["origin_server_ts"] = int(f"{time.time() * 1000}".split(".")[0])
|
||||
|
||||
signed_join = globals.hash_and_sign_event(join_event)
|
||||
|
||||
try:
|
||||
send_join_response = http_client.put(
|
||||
path=f"/_matrix/federation/v2/send_join/{room_id}/%24doesntmatter?omit_members=true",
|
||||
json=signed_join,
|
||||
destination=server_name,
|
||||
)
|
||||
|
||||
send_join_response.raise_for_status()
|
||||
|
||||
print("\nSuccess :)")
|
||||
except httpx.HTTPStatusError as e:
|
||||
print(
|
||||
f"HTTP error occurred during send_join: {e.response.status_code} - {e.response.text}"
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"An error occurred during send_join: {e}")
|
||||
Reference in New Issue
Block a user