KI-AGENT: Erste Matrix-Backendintegration ergänzen

This commit is contained in:
2026-05-18 15:37:12 +02:00
parent 54ae136f0d
commit b322d0c173
6 changed files with 250 additions and 1 deletions

View File

@@ -0,0 +1,201 @@
import { createHmac, randomBytes } from "node:crypto"
import { FastifyInstance } from "fastify"
import { authProfiles, authUsers } from "../../db/schema"
import { and, eq } from "drizzle-orm"
import { secrets } from "../utils/secrets"
type MatrixErrorResponse = {
errcode?: string
error?: string
}
const trimTrailingSlash = (value: string) => value.replace(/\/+$/, "")
export function matrixService(server: FastifyInstance) {
const homeserverUrl = () =>
trimTrailingSlash(
process.env.MATRIX_HOMESERVER_URL ||
secrets.MATRIX_HOMESERVER_URL ||
"http://localhost:8008"
)
const serverName = () =>
process.env.MATRIX_SERVER_NAME ||
secrets.MATRIX_SERVER_NAME ||
"localhost"
const registrationSharedSecret = () =>
process.env.MATRIX_REGISTRATION_SHARED_SECRET ||
secrets.MATRIX_REGISTRATION_SHARED_SECRET ||
""
const matrixLocalpartForUser = (userId: string) =>
`u_${userId.toLowerCase()}`
const matrixUserIdForUser = (userId: string) =>
`@${matrixLocalpartForUser(userId)}:${serverName()}`
const buildSharedSecretMac = (
nonce: string,
username: string,
password: string,
admin: boolean
) => {
const hmac = createHmac("sha1", registrationSharedSecret())
hmac.update(nonce)
hmac.update("\0")
hmac.update(username)
hmac.update("\0")
hmac.update(password)
hmac.update("\0")
hmac.update(admin ? "admin" : "notadmin")
return hmac.digest("hex")
}
const getCurrentUserDisplayName = async (userId: string, tenantId: number | null) => {
if (tenantId) {
const [profile] = await server.db
.select({
firstName: authProfiles.first_name,
lastName: authProfiles.last_name,
})
.from(authProfiles)
.where(and(
eq(authProfiles.user_id, userId),
eq(authProfiles.tenant_id, tenantId)
))
.limit(1)
const profileName = [profile?.firstName, profile?.lastName]
.filter(Boolean)
.join(" ")
.trim()
if (profileName) return profileName
}
const [user] = await server.db
.select({ email: authUsers.email })
.from(authUsers)
.where(eq(authUsers.id, userId))
.limit(1)
return user?.email || matrixUserIdForUser(userId)
}
const requestJson = async <T>(url: string, init?: RequestInit): Promise<T> => {
const response = await fetch(url, init)
const text = await response.text()
const body = text ? JSON.parse(text) : {}
if (!response.ok) {
const error = body as MatrixErrorResponse
throw Object.assign(
new Error(error.error || `Matrix request failed with ${response.status}`),
{
statusCode: response.status,
errcode: error.errcode,
body,
}
)
}
return body as T
}
const getStatus = async () => {
const configured = Boolean(homeserverUrl() && serverName())
if (!configured) {
return {
configured: false,
homeserverUrl: homeserverUrl(),
serverName: serverName(),
reachable: false,
}
}
try {
const versions = await requestJson<{ versions: string[] }>(
`${homeserverUrl()}/_matrix/client/versions`
)
return {
configured: true,
homeserverUrl: homeserverUrl(),
serverName: serverName(),
reachable: true,
versions: versions.versions,
}
} catch (err: any) {
return {
configured: true,
homeserverUrl: homeserverUrl(),
serverName: serverName(),
reachable: false,
error: err.message,
}
}
}
const provisionCurrentUser = async (userId: string, tenantId: number | null) => {
if (!registrationSharedSecret()) {
throw Object.assign(
new Error("MATRIX_REGISTRATION_SHARED_SECRET is not configured"),
{ statusCode: 503 }
)
}
const username = matrixLocalpartForUser(userId)
const matrixUserId = matrixUserIdForUser(userId)
const password = randomBytes(32).toString("base64url")
const displayName = await getCurrentUserDisplayName(userId, tenantId)
const nonceResponse = await requestJson<{ nonce: string }>(
`${homeserverUrl()}/_synapse/admin/v1/register`
)
const mac = buildSharedSecretMac(nonceResponse.nonce, username, password, false)
try {
await requestJson(`${homeserverUrl()}/_synapse/admin/v1/register`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
nonce: nonceResponse.nonce,
username,
password,
admin: false,
mac,
}),
})
return {
matrixUserId,
localpart: username,
displayName,
created: true,
alreadyExisted: false,
}
} catch (err: any) {
if (err.errcode === "M_USER_IN_USE") {
return {
matrixUserId,
localpart: username,
displayName,
created: false,
alreadyExisted: true,
}
}
throw err
}
}
return {
getStatus,
matrixUserIdForUser,
getCurrentUserDisplayName,
provisionCurrentUser,
}
}