mirror of
https://github.com/matrix-org/mjolnir.git
synced 2024-10-01 01:36:06 -04:00
AppService start
This commit is contained in:
parent
81cd91c250
commit
94040b00d6
@ -4,6 +4,7 @@ up:
|
||||
before:
|
||||
# Launch the reverse proxy, listening for connections *only* on the local host.
|
||||
- docker run --rm --network host --name mjolnir-test-reverse-proxy -p 127.0.0.1:8081:80 -v $MX_TEST_CWD/test/nginx.conf:/etc/nginx/nginx.conf:ro -d nginx
|
||||
- cp mjolnir-registration.yaml $MX_TEST_SYNAPSE_DIR/data/
|
||||
after:
|
||||
# Wait until Synapse is ready
|
||||
- until curl localhost:9999 --stderr /dev/null > /dev/null; do echo "Waiting for Synapse..."; sleep 1s; done
|
||||
@ -19,7 +20,7 @@ down:
|
||||
modules:
|
||||
- name: mjolnir
|
||||
build:
|
||||
- cp -r synapse_antispam $MX_TEST_MODULE_DIR
|
||||
- cp -r synapse_antispam $MX_TEST_MODULE_DIR/
|
||||
config:
|
||||
module: mjolnir.Module
|
||||
config: {}
|
||||
@ -34,6 +35,9 @@ homeserver:
|
||||
enable_registration: true
|
||||
enable_registration_without_verification: true
|
||||
|
||||
app_service_config_files:
|
||||
- "/data/mjolnir-registration.yaml"
|
||||
|
||||
# We remove rc_message so we can test rate limiting,
|
||||
# but we keep the others because of https://github.com/matrix-org/synapse/issues/11785
|
||||
# and we don't want to slow integration tests down.
|
||||
|
@ -24,6 +24,7 @@
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsdom": "^16.2.11",
|
||||
"@types/mocha": "^9.0.0",
|
||||
"@types/nedb": "^1.8.12",
|
||||
"@types/node": "^16.7.10",
|
||||
"@types/shell-quote": "1.7.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
@ -43,7 +44,8 @@
|
||||
"humanize-duration-ts": "^2.1.1",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^16.6.0",
|
||||
"matrix-bot-sdk": "^0.5.19",
|
||||
"matrix-appservice-bridge": "^4.0.1",
|
||||
"nedb": "^1.8.0",
|
||||
"parse-duration": "^1.0.2",
|
||||
"shell-quote": "^1.7.3",
|
||||
"yaml": "^2.1.1"
|
||||
|
101
src/AppService.ts
Normal file
101
src/AppService.ts
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
/**
|
||||
* What kind of vulnerabilities make process isolation count?
|
||||
* Either way they have the token to the appservice even with process isolation
|
||||
* the bounds are limitless.
|
||||
*
|
||||
* In both casees we have to migrate the configuration away from being static
|
||||
* so that it can be built on the fly to start new processes.
|
||||
* Yes we could also write to a file but that's disgusting and i refuse.
|
||||
* The config is the biggest piece of static bullshit that makes things a pita,
|
||||
* so this removes one of the arguments against in-process work.
|
||||
*
|
||||
* Ok so my idea is to just use fork and have a special Mjolnir instance
|
||||
* that basically proxies the mjolnir in the forked processes.
|
||||
*
|
||||
*/
|
||||
|
||||
import { randomUUID } from "crypto";
|
||||
import { AppServiceRegistration, Bridge, Cli, Request, WeakEvent, BridgeContext, MatrixUser, UserBridgeStore, RemoteUser } from "matrix-appservice-bridge";
|
||||
// needed by appservice irc, though it looks completely dead.
|
||||
import * as Datastore from "nedb";
|
||||
import { MjolnirManager } from "./appservice/MjolnirManager";
|
||||
//import config from "./config";
|
||||
// node index.js -r -u "http://localhost:9000" # remember to add the registration! you probably want host.docker.internal if using mx-tester
|
||||
// node index.js -p 9000
|
||||
|
||||
class MjolnirAppService {
|
||||
|
||||
public readonly bridge: Bridge;
|
||||
private readonly mjolnirManager: MjolnirManager = new MjolnirManager();
|
||||
|
||||
public constructor() {
|
||||
this.bridge = new Bridge({
|
||||
homeserverUrl: "http://localhost:9999",
|
||||
domain: "localhost",
|
||||
registration: "mjolnir-registration.yaml",
|
||||
controller: {
|
||||
onUserQuery: this.onUserQuery.bind(this),
|
||||
onEvent: this.onEvent.bind(this),
|
||||
},
|
||||
userStore: new UserBridgeStore(new Datastore()),
|
||||
});
|
||||
}
|
||||
|
||||
public async provisionNewMjolnir(requestingUserId: string) {
|
||||
// FIXME: we need to restrict who can do it (special list? ban remote users?)
|
||||
const issuedMjolnirs = await this.bridge.getUserStore()!.getRemoteUsersFromMatrixId(requestingUserId);
|
||||
if (issuedMjolnirs.length === 0) {
|
||||
// Now we need to make one of the transparent mjolnirs and add it to the monitor.
|
||||
const mjIntent = await this.bridge.getIntentFromLocalpart(`mjolnir_${randomUUID()}`);
|
||||
await mjIntent.ensureRegistered();
|
||||
// we're only doing this because it's complaining about missing profiles.
|
||||
// actually the user id wasn't even right, so this might not be necessary anymore.
|
||||
await mjIntent.ensureProfile('Mjolnir');
|
||||
this.mjolnirManager.createNew(requestingUserId, mjIntent);
|
||||
// Technically the mjolnir is a remote user, but also not because it's matrix-matrix.
|
||||
//const mjAsRemote = new RemoteUser(mjIntent.userId)
|
||||
//const bridgeStore = this.bridge.getUserStore()!;
|
||||
//bridgeStore.setRemoteUser()
|
||||
await this.bridge.getUserStore()!.linkUsers(new MatrixUser(requestingUserId), new RemoteUser(mjIntent.userId));
|
||||
} else {
|
||||
throw new Error(`User: ${requestingUserId} has already provisioned ${issuedMjolnirs.length} mjolnirs.`);
|
||||
}
|
||||
}
|
||||
|
||||
public onUserQuery (queriedUser: MatrixUser) {
|
||||
return {}; // auto-provision users with no additonal data
|
||||
}
|
||||
|
||||
// is it ok for this to be async? seems a bit dodge.
|
||||
// it should be BridgeRequestEvent not whatever this is
|
||||
public async onEvent(request: Request<WeakEvent>, context: BridgeContext) {
|
||||
// https://github.com/matrix-org/matrix-appservice-irc/blob/develop/src/bridge/MatrixHandler.ts#L921
|
||||
// ^ that's how matrix-appservice-irc maps from room to channel, we basically need to do the same but map
|
||||
// from room to which mjolnir it's for, unless that information is present in BridgeContext, which it might be...
|
||||
const mxEvent = request.getData();
|
||||
if ('m.room.member' === mxEvent.type) {
|
||||
if ('invite' === mxEvent.content['membership'] && mxEvent.state_key === this.bridge.botUserId) {
|
||||
await this.provisionNewMjolnir(mxEvent.sender);
|
||||
}
|
||||
}
|
||||
this.mjolnirManager.onEvent(request, context);
|
||||
}
|
||||
}
|
||||
|
||||
new Cli({
|
||||
registrationPath: "mjolnir-registration.yaml",
|
||||
generateRegistration: function(reg, callback) {
|
||||
reg.setId(AppServiceRegistration.generateToken());
|
||||
reg.setHomeserverToken(AppServiceRegistration.generateToken());
|
||||
reg.setAppServiceToken(AppServiceRegistration.generateToken());
|
||||
reg.setSenderLocalpart("mjolnir");
|
||||
reg.addRegexPattern("users", "@mjolnir_.*", true);
|
||||
callback(reg);
|
||||
},
|
||||
run: function(port: number) {
|
||||
const service = new MjolnirAppService();
|
||||
console.log("Matrix-side listening on port %s", port);
|
||||
service.bridge.run(port);
|
||||
}
|
||||
}).run();
|
101
src/appservice/MjolnirManager.ts
Normal file
101
src/appservice/MjolnirManager.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { Mjolnir } from "../Mjolnir";
|
||||
import { Intent, Request, WeakEvent, BridgeContext } from "matrix-appservice-bridge";
|
||||
import { IConfig, setDefaults } from "../config";
|
||||
import { SHORTCODE_EVENT_TYPE } from "../models/BanList";
|
||||
import { Permalinks } from "matrix-bot-sdk";
|
||||
|
||||
export class MjolnirManager {
|
||||
private readonly mjolnirs: Map</*the user id of the mjolnir*/string, ManagedMjolnir> = new Map();
|
||||
|
||||
public getDefaultMjolnirConfig(managementRoom: string): IConfig {
|
||||
return setDefaults({managementRoom});
|
||||
}
|
||||
|
||||
public async createNew(requestingUserId: string, intent: Intent) {
|
||||
// FIXME: We should be creating the intent here and generating the id surely?
|
||||
// rather than externally...
|
||||
// FIXME: We need to verify that we haven't stored a mjolnir already if we aren't doing the above.
|
||||
|
||||
// get mjolnir list wroking by just avoiding it for now and see if protections work
|
||||
// and bans.
|
||||
// Find out trade offs of changing mjolnir to make it work vs making new subcomponent of mjolnir.
|
||||
const managementRoomId = (await intent.createRoom({
|
||||
createAsClient: true,
|
||||
options: {
|
||||
preset: 'private_chat',
|
||||
invite: [requestingUserId],
|
||||
name: `${requestingUserId}'s mjolnir`
|
||||
}
|
||||
})).room_id;
|
||||
const managedMjolnir = new ManagedMjolnir(intent, await Mjolnir.setupMjolnirFromConfig(intent.matrixClient, this.getDefaultMjolnirConfig(managementRoomId)));
|
||||
await managedMjolnir.moveMeSomewhereCommonAndStopImplementingFunctionalityOnACommandFirstBasis(requestingUserId, 'list')
|
||||
this.mjolnirs.set(intent.userId, managedMjolnir);
|
||||
|
||||
}
|
||||
|
||||
public onEvent(request: Request<WeakEvent>, context: BridgeContext) {
|
||||
// We honestly don't know how we're going to map from bridge to user
|
||||
// https://github.com/matrix-org/matrix-appservice-bridge/blob/6046d31c54d461ad53e6d6e244ce2d944b62f890/src/components/room-bridge-store.ts
|
||||
// looks like it might work, but we will ask, figure it out later.
|
||||
[...this.mjolnirs.values()].forEach((mj: ManagedMjolnir) => mj.onEvent(request));
|
||||
}
|
||||
}
|
||||
|
||||
// Isolating this mjolnir is going to require provisioning an access token just for this user to be useful.
|
||||
// We can use fork and node's IPC to inform the process of matrix evnets.
|
||||
export class ManagedMjolnir {
|
||||
|
||||
|
||||
public constructor(private readonly intent: Intent, private readonly mjolnir: Mjolnir) {
|
||||
}
|
||||
|
||||
public async onEvent(request: Request<WeakEvent>) {
|
||||
// phony sync.
|
||||
const mxEvent = request.getData();
|
||||
if (mxEvent['type'] !== undefined) {
|
||||
this.mjolnir.client.emit('room.event', mxEvent.room_id, mxEvent);
|
||||
if (mxEvent.type === 'm.room.message') {
|
||||
this.mjolnir.client.emit('room.message', mxEvent.room_id, mxEvent);
|
||||
}
|
||||
// room.join requires us to know the joined rooms before so lol.
|
||||
}
|
||||
if (mxEvent['type'] === 'm.room.member') {
|
||||
if (mxEvent['content']['membership'] === 'invite' && mxEvent.state_key === this.intent.userId) {
|
||||
this.mjolnir.client.emit('room.invite', mxEvent.room_id, mxEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async moveMeSomewhereCommonAndStopImplementingFunctionalityOnACommandFirstBasis(mjolnirOwnerId: string, shortcode: string) {
|
||||
const powerLevels: { [key: string]: any } = {
|
||||
"ban": 50,
|
||||
"events": {
|
||||
"m.room.name": 100,
|
||||
"m.room.power_levels": 100,
|
||||
},
|
||||
"events_default": 50, // non-default
|
||||
"invite": 0,
|
||||
"kick": 50,
|
||||
"notifications": {
|
||||
"room": 20,
|
||||
},
|
||||
"redact": 50,
|
||||
"state_default": 50,
|
||||
"users": {
|
||||
[this.intent.userId]: 100,
|
||||
[mjolnirOwnerId]: 50
|
||||
},
|
||||
"users_default": 0,
|
||||
};
|
||||
|
||||
const listRoomId = await this.mjolnir.client.createRoom({
|
||||
preset: "public_chat",
|
||||
invite: [mjolnirOwnerId],
|
||||
initial_state: [{type: SHORTCODE_EVENT_TYPE, state_key: "", content: {shortcode: shortcode}}],
|
||||
power_level_content_override: powerLevels,
|
||||
});
|
||||
|
||||
const roomRef = Permalinks.forRoom(listRoomId);
|
||||
await this.mjolnir.watchList(roomRef);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user