KI-AGENT: Matrix-Login-Rate-Limits vermeiden
This commit is contained in:
@@ -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 (
|
||||||
|
|||||||
Reference in New Issue
Block a user