feat: Implement Matrix Chat Client

This commit is contained in:
Apple
2025-11-26 13:15:01 -08:00
parent 871812ef92
commit e9c04f6bcd
7 changed files with 1264 additions and 8 deletions

View File

@@ -67,6 +67,17 @@ class HealthResponse(BaseModel):
server_name: str
class UserTokenRequest(BaseModel):
user_id: str # DAARION user_id (UUID)
class UserTokenResponse(BaseModel):
matrix_user_id: str
access_token: str
device_id: str
home_server: str
async def get_admin_token() -> str:
"""Get or create admin access token for Matrix operations."""
global _admin_token
@@ -318,6 +329,105 @@ async def get_room_info(room_id: str):
raise HTTPException(status_code=503, detail="Matrix unavailable")
@app.post("/internal/matrix/users/token", response_model=UserTokenResponse)
async def get_user_token(request: UserTokenRequest):
"""
Get or create Matrix access token for a DAARION user.
This is used for chat bootstrap - allows frontend to connect to Matrix
on behalf of the user.
"""
# Generate Matrix username from DAARION user_id
user_id_short = request.user_id[:8].replace('-', '')
matrix_username = f"daarion_{user_id_short}"
matrix_user_id = f"@{matrix_username}:{settings.matrix_server_name}"
# Generate password (deterministic, based on user_id + secret)
matrix_password = hashlib.sha256(
f"{request.user_id}:{settings.synapse_registration_secret}".encode()
).hexdigest()[:32]
async with httpx.AsyncClient(timeout=30.0) as client:
try:
# Try to login first
login_resp = await client.post(
f"{settings.synapse_url}/_matrix/client/v3/login",
json={
"type": "m.login.password",
"identifier": {
"type": "m.id.user",
"user": matrix_username
},
"password": matrix_password,
"device_id": f"DAARION_{user_id_short}",
"initial_device_display_name": "DAARION Web"
}
)
if login_resp.status_code == 200:
result = login_resp.json()
logger.info(f"Matrix user logged in: {matrix_user_id}")
return UserTokenResponse(
matrix_user_id=result["user_id"],
access_token=result["access_token"],
device_id=result.get("device_id", f"DAARION_{user_id_short}"),
home_server=settings.matrix_server_name
)
# User doesn't exist, create via admin API
logger.info(f"Creating Matrix user: {matrix_username}")
# Get nonce
nonce_resp = await client.get(
f"{settings.synapse_url}/_synapse/admin/v1/register"
)
nonce_resp.raise_for_status()
nonce = nonce_resp.json()["nonce"]
# Generate MAC
mac = hmac.new(
key=settings.synapse_registration_secret.encode('utf-8'),
digestmod=hashlib.sha1
)
mac.update(nonce.encode('utf-8'))
mac.update(b"\x00")
mac.update(matrix_username.encode('utf-8'))
mac.update(b"\x00")
mac.update(matrix_password.encode('utf-8'))
mac.update(b"\x00")
mac.update(b"notadmin")
# Register user
register_resp = await client.post(
f"{settings.synapse_url}/_synapse/admin/v1/register",
json={
"nonce": nonce,
"username": matrix_username,
"password": matrix_password,
"admin": False,
"mac": mac.hexdigest()
}
)
if register_resp.status_code == 200:
result = register_resp.json()
logger.info(f"Matrix user created: {result['user_id']}")
return UserTokenResponse(
matrix_user_id=result["user_id"],
access_token=result["access_token"],
device_id=result.get("device_id", f"DAARION_{user_id_short}"),
home_server=settings.matrix_server_name
)
else:
error = register_resp.json()
logger.error(f"Failed to create Matrix user: {error}")
raise HTTPException(status_code=500, detail=f"Failed to create Matrix user: {error.get('error', 'Unknown')}")
except httpx.RequestError as e:
logger.error(f"Matrix request error: {e}")
raise HTTPException(status_code=503, detail="Matrix unavailable")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=settings.port)