KI-AGENT: Matrix-Login-Rate-Limits vermeiden

This commit is contained in:
2026-05-18 17:55:41 +02:00
parent b1e102ca5d
commit b03af21e97

View File

@@ -35,8 +35,18 @@ type MatrixUserSession = {
validUntilMs: number validUntilMs: number
} }
type MatrixCachedValue<T = any> = {
exists: true
cachedUntil: number
value: T
}
const matrixUserSessionCache = new Map<string, MatrixUserSession>() const matrixUserSessionCache = new Map<string, MatrixUserSession>()
const matrixJoinedRoomCache = new Map<string, number>() const matrixJoinedRoomCache = new Map<string, number>()
const matrixProvisionedUserCache = new Map<string, number>()
const matrixTenantSpaceCache = new Map<string, MatrixCachedValue>()
const matrixTenantRoomCache = new Map<string, MatrixCachedValue>()
let matrixServiceSessionCache: MatrixUserSession | null = null
const trimTrailingSlash = (value: string) => value.replace(/\/+$/, "") const trimTrailingSlash = (value: string) => value.replace(/\/+$/, "")
const readLocalDevRegistrationSharedSecret = () => { const readLocalDevRegistrationSharedSecret = () => {
@@ -229,6 +239,10 @@ export function matrixService(server: FastifyInstance) {
) )
} }
if (matrixServiceSessionCache && matrixServiceSessionCache.validUntilMs > Date.now() + 60_000) {
return matrixServiceSessionCache
}
const username = serviceUserLocalpart() const username = serviceUserLocalpart()
const password = serviceUserPassword() const password = serviceUserPassword()
@@ -240,7 +254,14 @@ export function matrixService(server: FastifyInstance) {
} }
} }
return loginMatrixUser(username, password) const login = await loginMatrixUser(username, password)
matrixServiceSessionCache = {
accessToken: login.access_token,
matrixUserId: login.user_id,
validUntilMs: Date.now() + 30 * 60 * 1000,
}
return matrixServiceSessionCache
} }
const getCurrentUserDisplayName = async (userId: string, tenantId: number | null) => { const getCurrentUserDisplayName = async (userId: string, tenantId: number | null) => {
@@ -320,7 +341,7 @@ export function matrixService(server: FastifyInstance) {
const login = await requestMatrixJson<{ access_token: string }>( const login = await requestMatrixJson<{ access_token: string }>(
`/_synapse/admin/v1/users/${encodeURIComponent(matrixUserId)}/login`, `/_synapse/admin/v1/users/${encodeURIComponent(matrixUserId)}/login`,
serviceLogin.access_token, serviceLogin.accessToken,
{ {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -386,11 +407,25 @@ export function matrixService(server: FastifyInstance) {
const username = await matrixLocalpartForUser(userId, tenantId) const username = await matrixLocalpartForUser(userId, tenantId)
const matrixUserId = await matrixUserIdForUser(userId, tenantId) const matrixUserId = await matrixUserIdForUser(userId, tenantId)
const password = randomBytes(32).toString("base64url")
const displayName = await getCurrentUserDisplayName(userId, tenantId) const displayName = await getCurrentUserDisplayName(userId, tenantId)
const cacheKey = `${tenantId || "global"}:${userId}`
const cachedUntil = matrixProvisionedUserCache.get(cacheKey)
if (cachedUntil && cachedUntil > Date.now()) {
return {
matrixUserId,
localpart: username,
displayName,
created: false,
alreadyExisted: true,
}
}
const password = randomBytes(32).toString("base64url")
try { try {
await registerWithSharedSecret(username, password, false) await registerWithSharedSecret(username, password, false)
matrixProvisionedUserCache.set(cacheKey, Date.now() + 30 * 60 * 1000)
return { return {
matrixUserId, matrixUserId,
@@ -401,6 +436,8 @@ export function matrixService(server: FastifyInstance) {
} }
} catch (err: any) { } catch (err: any) {
if (err.errcode === "M_USER_IN_USE") { if (err.errcode === "M_USER_IN_USE") {
matrixProvisionedUserCache.set(cacheKey, Date.now() + 30 * 60 * 1000)
return { return {
matrixUserId, matrixUserId,
localpart: username, localpart: username,
@@ -478,23 +515,37 @@ export function matrixService(server: FastifyInstance) {
const provisionCurrentTenantSpace = async (userId: string, tenantId: number | null) => { const provisionCurrentTenantSpace = async (userId: string, tenantId: number | null) => {
const tenant = await getCurrentTenant(tenantId) const tenant = await getCurrentTenant(tenantId)
const cacheKey = String(tenant.id)
const cachedSpace = matrixTenantSpaceCache.get(cacheKey)
if (cachedSpace?.exists && cachedSpace.cachedUntil > Date.now()) {
return cachedSpace.value
}
const existing = await getTenantSpaceStatus(tenant.id) const existing = await getTenantSpaceStatus(tenant.id)
const userAccount = await provisionCurrentUser(userId, tenant.id) const userAccount = await provisionCurrentUser(userId, tenant.id)
if (existing.exists) { if (existing.exists) {
return { const value = {
...existing, ...existing,
created: false, created: false,
alreadyExisted: true, alreadyExisted: true,
invitedUserId: userAccount.matrixUserId, invitedUserId: userAccount.matrixUserId,
} }
matrixTenantSpaceCache.set(cacheKey, {
exists: true,
cachedUntil: Date.now() + 30 * 60 * 1000,
value,
})
return value
} }
const serviceLogin = await ensureServiceAccessToken() const serviceLogin = await ensureServiceAccessToken()
const aliasLocalpart = tenantSpaceAliasLocalpart(tenant) const aliasLocalpart = tenantSpaceAliasLocalpart(tenant)
const createdRoom = await requestMatrixJson<{ room_id: string }>( const createdRoom = await requestMatrixJson<{ room_id: string }>(
"/_matrix/client/v3/createRoom", "/_matrix/client/v3/createRoom",
serviceLogin.access_token, serviceLogin.accessToken,
{ {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -521,7 +572,7 @@ export function matrixService(server: FastifyInstance) {
} }
) )
return { const value = {
tenantId: tenant.id, tenantId: tenant.id,
tenantName: tenant.name, tenantName: tenant.name,
alias: tenantSpaceAlias(tenant), alias: tenantSpaceAlias(tenant),
@@ -530,8 +581,15 @@ export function matrixService(server: FastifyInstance) {
alreadyExisted: false, alreadyExisted: false,
roomId: createdRoom.room_id, roomId: createdRoom.room_id,
invitedUserId: userAccount.matrixUserId, invitedUserId: userAccount.matrixUserId,
serviceUserId: serviceLogin.user_id, serviceUserId: serviceLogin.matrixUserId,
} }
matrixTenantSpaceCache.set(cacheKey, {
exists: true,
cachedUntil: Date.now() + 30 * 60 * 1000,
value,
})
return value
} }
const getTenantRoomStatus = async ( const getTenantRoomStatus = async (
@@ -589,24 +647,38 @@ export function matrixService(server: FastifyInstance) {
const key = normalizeMatrixAliasSeed(options.key || options.name || "allgemein") const key = normalizeMatrixAliasSeed(options.key || options.name || "allgemein")
const name = (options.name || "Allgemeiner Chat").trim() || "Allgemeiner Chat" const name = (options.name || "Allgemeiner Chat").trim() || "Allgemeiner Chat"
const topic = (options.topic || `Allgemeiner Kommunikationsraum für ${tenant.name}`).trim() const topic = (options.topic || `Allgemeiner Kommunikationsraum für ${tenant.name}`).trim()
const cacheKey = `${tenant.id}:${key}`
const cachedRoom = matrixTenantRoomCache.get(cacheKey)
if (cachedRoom?.exists && cachedRoom.cachedUntil > Date.now()) {
return cachedRoom.value
}
const existing = await getTenantRoomStatus(tenant.id, key, name) const existing = await getTenantRoomStatus(tenant.id, key, name)
const userAccount = await provisionCurrentUser(userId, tenant.id) const userAccount = await provisionCurrentUser(userId, tenant.id)
const tenantSpace = await provisionCurrentTenantSpace(userId, tenant.id) const tenantSpace = await provisionCurrentTenantSpace(userId, tenant.id)
if (existing.exists) { if (existing.exists) {
return { const value = {
...existing, ...existing,
created: false, created: false,
alreadyExisted: true, alreadyExisted: true,
parentSpaceRoomId: tenantSpace.roomId, parentSpaceRoomId: tenantSpace.roomId,
invitedUserId: userAccount.matrixUserId, invitedUserId: userAccount.matrixUserId,
} }
matrixTenantRoomCache.set(cacheKey, {
exists: true,
cachedUntil: Date.now() + 30 * 60 * 1000,
value,
})
return value
} }
const serviceLogin = await ensureServiceAccessToken() const serviceLogin = await ensureServiceAccessToken()
const createdRoom = await requestMatrixJson<{ room_id: string }>( const createdRoom = await requestMatrixJson<{ room_id: string }>(
"/_matrix/client/v3/createRoom", "/_matrix/client/v3/createRoom",
serviceLogin.access_token, serviceLogin.accessToken,
{ {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -640,7 +712,7 @@ export function matrixService(server: FastifyInstance) {
await requestMatrixJson( await requestMatrixJson(
`/_matrix/client/v3/rooms/${encodeURIComponent(tenantSpace.roomId)}/state/m.space.child/${encodeURIComponent(createdRoom.room_id)}`, `/_matrix/client/v3/rooms/${encodeURIComponent(tenantSpace.roomId)}/state/m.space.child/${encodeURIComponent(createdRoom.room_id)}`,
serviceLogin.access_token, serviceLogin.accessToken,
{ {
method: "PUT", method: "PUT",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
@@ -652,7 +724,7 @@ export function matrixService(server: FastifyInstance) {
} }
) )
return { const value = {
tenantId: tenant.id, tenantId: tenant.id,
tenantName: tenant.name, tenantName: tenant.name,
key, key,
@@ -664,8 +736,15 @@ export function matrixService(server: FastifyInstance) {
roomId: createdRoom.room_id, roomId: createdRoom.room_id,
parentSpaceRoomId: tenantSpace.roomId, parentSpaceRoomId: tenantSpace.roomId,
invitedUserId: userAccount.matrixUserId, invitedUserId: userAccount.matrixUserId,
serviceUserId: serviceLogin.user_id, serviceUserId: serviceLogin.matrixUserId,
} }
matrixTenantRoomCache.set(cacheKey, {
exists: true,
cachedUntil: Date.now() + 30 * 60 * 1000,
value,
})
return value
} }
const ensureCurrentUserJoinedRoom = async ( const ensureCurrentUserJoinedRoom = async (