KI-AGENT: Matrix-Kontoerstellung nutzbarer machen

This commit is contained in:
2026-05-18 16:59:35 +02:00
parent eb2dd03ef9
commit c893574cb1
4 changed files with 95 additions and 10 deletions

View File

@@ -1,4 +1,6 @@
import { createHmac, randomBytes } from "node:crypto"
import { createHash, createHmac, randomBytes } from "node:crypto"
import { existsSync, readFileSync } from "node:fs"
import { resolve } from "node:path"
import { FastifyInstance } from "fastify"
import { authProfiles, authUsers } from "../../db/schema"
import { and, eq } from "drizzle-orm"
@@ -10,6 +12,43 @@ type MatrixErrorResponse = {
}
const trimTrailingSlash = (value: string) => value.replace(/\/+$/, "")
const readLocalDevRegistrationSharedSecret = () => {
if (process.env.NODE_ENV === "production") return ""
const candidates = [
resolve(process.cwd(), "../matrix/dev/synapse/homeserver.yaml"),
resolve(process.cwd(), "matrix/dev/synapse/homeserver.yaml"),
]
for (const candidate of candidates) {
if (!existsSync(candidate)) continue
const content = readFileSync(candidate, "utf8")
const match = content.match(/^registration_shared_secret:\s*["']?(.+?)["']?\s*$/m)
if (match?.[1]) {
return match[1]
}
}
return ""
}
const normalizeMatrixLocalpartSeed = (value: string) => {
const normalized = value
.toLowerCase()
.normalize("NFKD")
.replace(/[\u0300-\u036f]/g, "")
.replace(/ä/g, "a")
.replace(/ö/g, "o")
.replace(/ü/g, "u")
.replace(/ß/g, "ss")
.replace(/[^a-z0-9._=-]+/g, "_")
.replace(/_+/g, "_")
.replace(/^[._=-]+|[._=-]+$/g, "")
return normalized || "user"
}
export function matrixService(server: FastifyInstance) {
const homeserverUrl = () =>
@@ -27,13 +66,38 @@ export function matrixService(server: FastifyInstance) {
const registrationSharedSecret = () =>
process.env.MATRIX_REGISTRATION_SHARED_SECRET ||
secrets.MATRIX_REGISTRATION_SHARED_SECRET ||
readLocalDevRegistrationSharedSecret() ||
""
const matrixLocalpartForUser = (userId: string) =>
`u_${userId.toLowerCase()}`
const getUserIdentitySeed = async (userId: string, tenantId: number | null) => {
const [user] = await server.db
.select({ email: authUsers.email })
.from(authUsers)
.where(eq(authUsers.id, userId))
.limit(1)
const matrixUserIdForUser = (userId: string) =>
`@${matrixLocalpartForUser(userId)}:${serverName()}`
if (user?.email) {
return user.email.split("@")[0] || user.email
}
if (tenantId) {
const displayName = await getCurrentUserDisplayName(userId, tenantId)
if (displayName && !displayName.startsWith("@")) {
return displayName
}
}
return "user"
}
const matrixLocalpartForUser = async (userId: string, tenantId: number | null) => {
const seed = normalizeMatrixLocalpartSeed(await getUserIdentitySeed(userId, tenantId))
const hash = createHash("sha256").update(userId).digest("hex").slice(0, 8)
return `${seed}_${hash}`
}
const matrixUserIdForUser = async (userId: string, tenantId: number | null) =>
`@${await matrixLocalpartForUser(userId, tenantId)}:${serverName()}`
const buildSharedSecretMac = (
nonce: string,
@@ -80,7 +144,7 @@ export function matrixService(server: FastifyInstance) {
.where(eq(authUsers.id, userId))
.limit(1)
return user?.email || matrixUserIdForUser(userId)
return user?.email || await matrixUserIdForUser(userId, tenantId)
}
const requestJson = async <T>(url: string, init?: RequestInit): Promise<T> => {
@@ -111,6 +175,7 @@ export function matrixService(server: FastifyInstance) {
configured: false,
homeserverUrl: homeserverUrl(),
serverName: serverName(),
provisioningConfigured: Boolean(registrationSharedSecret()),
reachable: false,
}
}
@@ -124,6 +189,7 @@ export function matrixService(server: FastifyInstance) {
configured: true,
homeserverUrl: homeserverUrl(),
serverName: serverName(),
provisioningConfigured: Boolean(registrationSharedSecret()),
reachable: true,
versions: versions.versions,
}
@@ -132,6 +198,7 @@ export function matrixService(server: FastifyInstance) {
configured: true,
homeserverUrl: homeserverUrl(),
serverName: serverName(),
provisioningConfigured: Boolean(registrationSharedSecret()),
reachable: false,
error: err.message,
}
@@ -146,8 +213,8 @@ export function matrixService(server: FastifyInstance) {
)
}
const username = matrixLocalpartForUser(userId)
const matrixUserId = matrixUserIdForUser(userId)
const username = await matrixLocalpartForUser(userId, tenantId)
const matrixUserId = await matrixUserIdForUser(userId, tenantId)
const password = randomBytes(32).toString("base64url")
const displayName = await getCurrentUserDisplayName(userId, tenantId)

View File

@@ -12,7 +12,7 @@ export default async function communicationRoutes(server: FastifyInstance) {
const userId = req.user.user_id
return {
matrixUserId: matrix.matrixUserIdForUser(userId),
matrixUserId: await matrix.matrixUserIdForUser(userId, req.user.tenant_id),
displayName: await matrix.getCurrentUserDisplayName(userId, req.user.tenant_id),
}
})