From 7630cc89a8b9a7f11456ede584241e8ccd17b022 Mon Sep 17 00:00:00 2001 From: Pavel Baluev Date: Thu, 18 Jan 2024 15:59:59 +0100 Subject: [PATCH] Test "rotate-token" command --- src/admin-api/index.ts | 16 +++++++++- src/admin-api/types.ts | 4 +++ src/bot.ts | 3 ++ src/commands/rotate-token.ts | 58 ++++++++++++++++++++++++++++++++++++ 4 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/commands/rotate-token.ts diff --git a/src/admin-api/index.ts b/src/admin-api/index.ts index c6886d4..3f79306 100644 --- a/src/admin-api/index.ts +++ b/src/admin-api/index.ts @@ -4,6 +4,7 @@ import config from "src/config/env" import { ListRoomResponse, + LoginUserResponse, RoomDeletionResponse, RoomInfoResponse, RoomInfoShort, @@ -58,7 +59,9 @@ class AdminApi { async getRoomPowerLevelsEvent(roomId: string): Promise { try { const data = await this.getRoomState(roomId) - const powerLevelEvent = data.state.find((x) => x.type === "m.room.power_levels") as unknown as RoomPowerLevelsEvent + const powerLevelEvent = data.state.find( + (x) => x.type === "m.room.power_levels", + ) as unknown as RoomPowerLevelsEvent if (!powerLevelEvent) { return null } @@ -136,6 +139,17 @@ class AdminApi { } while (loop * limit <= total) return rooms } + + async loginUser(userId: string): Promise { + try { + return (await this.makeRequest("GET", `/v1/users/${userId}/login`)) as LoginUserResponse + } catch (err) { + if (err.response.status === 404) { + return null + } + throw err + } + } } export const adminApi = new AdminApi({ host: config.MATRIX_SERVER_URL, accessToken: config.ACCESS_TOKEN }) diff --git a/src/admin-api/types.ts b/src/admin-api/types.ts index 9762a92..0c33f2a 100644 --- a/src/admin-api/types.ts +++ b/src/admin-api/types.ts @@ -185,3 +185,7 @@ export type CommandReport = { succeedInvites: string[] skippedInvitesNumber: number } + +export type LoginUserResponse = { + access_token: string +} diff --git a/src/bot.ts b/src/bot.ts index 7de40cc..06263f7 100644 --- a/src/bot.ts +++ b/src/bot.ts @@ -13,6 +13,7 @@ import { INVITE_COMMAND, runInviteCommand } from "./commands/invite" import { INVITE_ROOM, runInviteRoomCommand } from "./commands/invite-room" import { PROMOTE_COMMAND, runPromoteCommand } from "./commands/promote" import { runSpaceCommand, SPACE_COMMAND } from "./commands/space" +import { runRotateTokenCommand, ROTATE_TOKEN_COMMAND } from "./commands/rotate-token" import { CommandError } from "./utils" /* This is the maximum allowed time between time on matrix server @@ -113,6 +114,8 @@ export default class Bot { return await runDeactivateUserCommand(roomId, event, args, this.client) case SPACE_COMMAND: return await runSpaceCommand(roomId, event, args, this.client) + case ROTATE_TOKEN_COMMAND: + return await runRotateTokenCommand(roomId, event, args, this.client) default: return await runHelpCommand(roomId, event, this.client) } diff --git a/src/commands/rotate-token.ts b/src/commands/rotate-token.ts new file mode 100644 index 0000000..c449113 --- /dev/null +++ b/src/commands/rotate-token.ts @@ -0,0 +1,58 @@ +import { LogService, MatrixClient, MessageEvent, MessageEventContent } from "matrix-bot-sdk" + +import { adminApi } from "src/admin-api" +import { UserAccountResponse } from "src/admin-api/types" +import config from "src/config/env" +import { canExecuteCommand, CommandError } from "src/utils" + +const moduleName = "RotateTokenCommand" +export const ROTATE_TOKEN_COMMAND = "rotate-token" + +export async function runRotateTokenCommand( + roomId: string, + event: MessageEvent, + args: string[], + client: MatrixClient, +): Promise { + // Ensure the user can execute the command + const canExecute = await canExecuteCommand(event.sender, roomId) + if (!canExecute) { + throw new CommandError(`Access denied`) + } + + // 1. Retrive and validate arguments + const [, targetUserId] = args + if (!event.sender.includes(`:${config.MATRIX_SERVER_DOMAIN}`)) { + throw new CommandError(`Access denied.`) + } + if (!targetUserId || !targetUserId.includes(`:${config.MATRIX_SERVER_DOMAIN}`)) { + const [, wrongHomeServer] = targetUserId.split(":") + throw new CommandError( + `The provided user handle is not registered under ${config.MATRIX_SERVER_DOMAIN}, but ${wrongHomeServer}. \nMake sure that the user handle ends with ":${config.MATRIX_SERVER_DOMAIN}"`, + ) + } + + // 2. Retrieve user details + let user: UserAccountResponse | null = null + try { + user = await adminApi.getUserAccount(targetUserId) + } catch (e) { + LogService.error(moduleName, e) + throw new CommandError(`Unable to retrieve user account details.`) + } + if (!user) { + throw new CommandError(`The user "${targetUserId}" cannot be found.`) + } + + // 3. Generate an access token + try { + const response = await adminApi.loginUser(targetUserId) + await client.sendHtmlText( + roomId, + `New access token for user "${targetUserId}": ${JSON.stringify(response)}`, + ) + } catch (err) { + LogService.error(moduleName, err) + throw new CommandError(`Unable to retrieve user access token.`) + } +}