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