diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d62baf90..c63b1354 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -37,8 +37,8 @@ jobs: uses: actions/setup-node@v1 with: node-version: 16 - - run: yarn --ignore-scripts && yarn build:app - - run: node lib/Config/Defaults.js --config | diff config.sample.yml - + - run: yarn --ignore-scripts + - run: yarn --silent ts-node src/Config/Defaults.ts --config | diff config.sample.yml - metrics-docs: runs-on: ubuntu-latest diff --git a/changelog.d/102.feature b/changelog.d/102.feature new file mode 100644 index 00000000..075ff196 --- /dev/null +++ b/changelog.d/102.feature @@ -0,0 +1 @@ +Allow running multiple resources on the same HTTP listener. See the new `listeners` config. diff --git a/config.sample.yml b/config.sample.yml index 62ae7357..832856db 100644 --- a/config.sample.yml +++ b/config.sample.yml @@ -54,16 +54,9 @@ generic: urlPrefix: https://example.com/mywebhookspath/ allowJsTransformationFunctions: false userIdPrefix: webhooks_ -webhook: - # HTTP webhook listener options - # - port: 9000 - bindAddress: 0.0.0.0 provisioning: # (Optional) Provisioning API for integration managers # - bindAddress: 127.0.0.1 - port: 9001 secret: "!secretToken" passFile: # A passkey used to encrypt tokens stored inside the bridge. @@ -79,8 +72,6 @@ metrics: # (Optional) Prometheus metrics support # enabled: true - bindAddress: 127.0.0.1 - port: 9002 queue: # (Optional) Message queue / cache configuration options for large scale deployments # @@ -91,4 +82,19 @@ logging: # (Optional) Logging settings. You can have a severity debug,info,warn,error # level: info +listeners: + # (Optional) HTTP Listener configuration. + # Bind resource endpoints to ports and addresses. + # 'resources' may be any of webhooks, widgets, metrics, provisioning, appservice + # + - port: 9000 + bindAddress: 0.0.0.0 + resources: + - webhooks + - widgets + - port: 9001 + bindAddress: 127.0.0.1 + resources: + - metrics + - provisioning diff --git a/docs/setup.md b/docs/setup.md index 08b4f523..39c7fbb1 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -58,17 +58,30 @@ Copy `registration.sample.yml` into `registration.yml` and fill in: You will need to link the registration file to the homeserver. Consult your homeserver documentation on how to add appservices. [Synapse documents the process here](https://matrix-org.github.io/synapse/latest/application_services.html) -### Webhooks configuration +### Listeners configuration -You will need to configure the webhooks listener at a minimum to make the bridge functional. -To do this, ensure that your configuration for `webhook` is correct: +You will need to configure some listeners to make the bridge functional. ```yaml -webhook: - port: 5061 - host: 0.0.0.0 + # (Optional) HTTP Listener configuration. + # Bind resource endpoints to ports and addresses. + # 'resources' may be any of webhooks, widgets, metrics, provisioning, appservice + # + - port: 9000 + bindAddress: 0.0.0.0 + resources: + - webhooks + - widgets + - port: 9001 + bindAddress: 127.0.0.1 + resources: + - metrics + - provisioning ``` +At a minimum, you should bind the `webooks` resource to a port and address. You can have multiple resources on the same +port, or one on each. + You will also need to make this port accessible to the internet so services like GitHub can reach the bridge. It is reccomended to factor hookshot into your load balancer configuration, but currrently this process is left as an excercise to the user. diff --git a/package.json b/package.json index c5b93536..f744d80a 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "lint": "yarn run lint:js && yarn run lint:rs", "lint:js": "eslint -c .eslintrc.js src/**/*.ts", "lint:rs": "cargo fmt --all -- --check", - "generate-default-config": "node lib/Config/Defaults.js --config > config.sample.yml" + "generate-default-config": "ts-node src/Config/Defaults.ts --config > config.sample.yml" }, "dependencies": { "@alloc/quick-lru": "^5.2.0", diff --git a/src/App/BridgeApp.ts b/src/App/BridgeApp.ts index 1e40d7f2..87a07af8 100644 --- a/src/App/BridgeApp.ts +++ b/src/App/BridgeApp.ts @@ -5,6 +5,7 @@ import { BridgeConfig, parseRegistrationFile } from "../Config/Config"; import { Webhooks } from "../Webhooks"; import { MatrixSender } from "../MatrixSender"; import { UserNotificationWatcher } from "../Notifications/UserNotificationWatcher"; +import { ListenerService } from "../ListenerService"; LogWrapper.configureLogging("debug"); const log = new LogWrapper("App"); @@ -14,24 +15,27 @@ async function start() { const registrationFile = process.argv[3] || "./registration.yml"; const config = await BridgeConfig.parseConfig(configFile, process.env); const registration = await parseRegistrationFile(registrationFile); + const listener = new ListenerService(config.listeners); LogWrapper.configureLogging(config.logging.level); if (config.queue.monolithic) { const webhookHandler = new Webhooks(config); - webhookHandler.listen(); + listener.bindResource('webhooks', webhookHandler.expressRouter); const matrixSender = new MatrixSender(config, registration); matrixSender.listen(); const userNotificationWatcher = new UserNotificationWatcher(config); userNotificationWatcher.start(); } - const bridgeApp = new Bridge(config, registration); + const bridgeApp = new Bridge(config, registration, listener); process.once("SIGTERM", () => { log.error("Got SIGTERM"); + listener.stop(); bridgeApp.stop(); }); await bridgeApp.start(); + listener.start(); } start().catch((ex) => { diff --git a/src/App/GithubWebhookApp.ts b/src/App/GithubWebhookApp.ts index 3cc88443..0d0f165e 100644 --- a/src/App/GithubWebhookApp.ts +++ b/src/App/GithubWebhookApp.ts @@ -3,6 +3,7 @@ import { Webhooks } from "../Webhooks"; import LogWrapper from "../LogWrapper"; import { UserNotificationWatcher } from "../Notifications/UserNotificationWatcher"; import Metrics from "../Metrics"; +import { ListenerService } from "../ListenerService"; const log = new LogWrapper("App"); @@ -11,22 +12,24 @@ async function start() { const configFile = process.argv[2] || "./config.yml"; const config = await BridgeConfig.parseConfig(configFile, process.env); LogWrapper.configureLogging(config.logging.level); + const listener = new ListenerService(config.listeners); if (config.metrics) { if (!config.metrics.port) { log.warn(`Not running metrics for service, no port specified`); } else { - Metrics.start(config.metrics); + listener.bindResource('metrics', Metrics.expressRouter); } } const webhookHandler = new Webhooks(config); - webhookHandler.listen(); + listener.bindResource('webhooks', webhookHandler.expressRouter); const userWatcher = new UserNotificationWatcher(config); userWatcher.start(); + listener.start(); process.once("SIGTERM", () => { log.error("Got SIGTERM"); webhookHandler.stop(); + listener.stop(); userWatcher.stop(); - Metrics.stop(); }); } diff --git a/src/App/MatrixSenderApp.ts b/src/App/MatrixSenderApp.ts index 9f1ae4a2..d2c78bd2 100644 --- a/src/App/MatrixSenderApp.ts +++ b/src/App/MatrixSenderApp.ts @@ -2,6 +2,7 @@ import { BridgeConfig, parseRegistrationFile } from "../Config/Config"; import { MatrixSender } from "../MatrixSender"; import LogWrapper from "../LogWrapper"; import Metrics from "../Metrics"; +import { ListenerService } from "../ListenerService"; const log = new LogWrapper("App"); @@ -12,19 +13,21 @@ async function start() { const config = await BridgeConfig.parseConfig(configFile, process.env); const registration = await parseRegistrationFile(registrationFile); LogWrapper.configureLogging(config.logging.level); + const listener = new ListenerService(config.listeners); const sender = new MatrixSender(config, registration); if (config.metrics) { if (!config.metrics.port) { log.warn(`Not running metrics for service, no port specified`); } else { - Metrics.start(config.metrics); + listener.bindResource('metrics', Metrics.expressRouter); } } sender.listen(); + listener.start(); process.once("SIGTERM", () => { log.error("Got SIGTERM"); sender.stop(); - Metrics.stop(); + listener.stop(); }); } diff --git a/src/Bridge.ts b/src/Bridge.ts index 3310073d..eb502fec 100644 --- a/src/Bridge.ts +++ b/src/Bridge.ts @@ -35,6 +35,7 @@ import { OAuthRequest } from "./WebhookTypes"; import { promises as fs } from "fs"; import { SetupConnection } from "./Connections/SetupConnection"; import Metrics from "./Metrics"; +import { ListenerService } from "./ListenerService"; const log = new LogWrapper("Bridge"); export function getAppservice(config: BridgeConfig, registration: IAppserviceRegistration, storage: IAppserviceStorageProvider) { @@ -77,7 +78,7 @@ export class Bridge { private ready = false; - constructor(private config: BridgeConfig, private registration: IAppserviceRegistration) { + constructor(private config: BridgeConfig, private registration: IAppserviceRegistration, private readonly listener: ListenerService) { if (this.config.queue.host && this.config.queue.port) { log.info(`Initialising Redis storage (on ${this.config.queue.host}:${this.config.queue.port})`); this.storage = new RedisStorageProvider(this.config.queue.host, this.config.queue.port); @@ -95,10 +96,7 @@ export class Bridge { public stop() { this.as.stop(); - Metrics.stop(); if (this.queue.stop) this.queue.stop(); - if (this.widgetApi) this.widgetApi.stop(); - if (this.provisioningApi) this.provisioningApi.stop(); } public async start() { @@ -578,13 +576,13 @@ export class Bridge { } if (this.config.widgets) { - await this.widgetApi.start(this.config.widgets.port); + this.listener.bindResource('widgets', this.widgetApi.expressRouter); } if (this.provisioningApi) { - await this.provisioningApi.listen(); + this.listener.bindResource('provisioning', this.provisioningApi.expressRouter); } if (this.config.metrics?.enabled) { - Metrics.start(this.config.metrics, this.as); + this.listener.bindResource('metrics', Metrics.expressRouter); } await this.as.begin(); log.info("Started bridge"); diff --git a/src/Config/Config.ts b/src/Config/Config.ts index fc89120e..571fcc33 100644 --- a/src/Config/Config.ts +++ b/src/Config/Config.ts @@ -3,6 +3,7 @@ import { promises as fs } from "fs"; import { IAppserviceRegistration } from "matrix-bot-sdk"; import * as assert from "assert"; import { configKey } from "./Decorators"; +import { BridgeConfigListener, ResourceTypeArray } from "../ListenerService"; import { GitHubRepoConnectionOptions } from "../Connections/GithubRepo"; interface BridgeConfigGitHubYAML { @@ -92,11 +93,12 @@ export interface BridgeGenericWebhooksConfig { } interface BridgeWidgetConfig { - port: number; + port?: number; addToAdminRooms: boolean; publicUrl: string; } + interface BridgeConfigBridge { domain: string; url: string; @@ -111,8 +113,8 @@ interface BridgeConfigBridge { } interface BridgeConfigWebhook { - port: number; - bindAddress: string; + port?: number; + bindAddress?: string; } interface BridgeConfigQueue { @@ -132,7 +134,7 @@ interface BridgeConfigBot { export interface BridgeConfigProvisioning { bindAddress?: string; - port: number; + port?: number; secret: string; } @@ -142,7 +144,6 @@ export interface BridgeConfigMetrics { port?: number; } - interface BridgeConfigRoot { bot?: BridgeConfigBot; bridge: BridgeConfigBridge; @@ -154,16 +155,15 @@ interface BridgeConfigRoot { logging: BridgeConfigLogging; passFile: string; queue: BridgeConfigQueue; - webhook: BridgeConfigWebhook; + webhook?: BridgeConfigWebhook; widgets?: BridgeWidgetConfig; metrics?: BridgeConfigMetrics; + listeners?: BridgeConfigListener[]; } export class BridgeConfig { @configKey("Basic homeserver configuration") public readonly bridge: BridgeConfigBridge; - @configKey("HTTP webhook listener options") - public readonly webhook: BridgeConfigWebhook; @configKey("Message queue / cache configuration options for large scale deployments", true) public readonly queue: BridgeConfigQueue; @configKey("Logging settings. You can have a severity debug,info,warn,error", true) @@ -188,6 +188,11 @@ export class BridgeConfig { @configKey("Prometheus metrics support", true) public readonly metrics?: BridgeConfigMetrics; + @configKey(`HTTP Listener configuration. + Bind resource endpoints to ports and addresses. + 'resources' may be any of ${ResourceTypeArray.join(', ')}`, true) + public readonly listeners: BridgeConfigListener[]; + constructor(configData: BridgeConfigRoot, env: {[key: string]: string|undefined}) { this.bridge = configData.bridge; assert.ok(this.bridge); @@ -201,12 +206,10 @@ export class BridgeConfig { this.gitlab = configData.gitlab; this.jira = configData.jira; this.generic = configData.generic; - this.webhook = configData.webhook; this.provisioning = configData.provisioning; this.passFile = configData.passFile; this.bot = configData.bot; this.metrics = configData.metrics; - assert.ok(this.webhook); this.queue = configData.queue || { monolithic: true, }; @@ -220,6 +223,41 @@ export class BridgeConfig { this.queue.port = env.CFG_QUEUE_POST ? parseInt(env.CFG_QUEUE_POST, 10) : undefined; } + // Listeners is a bit special + this.listeners = configData.listeners || []; + + // For legacy reasons, copy across the per-service listener config into the listeners array. + if (configData.webhook?.port) { + this.listeners.push({ + resources: ['webhooks'], + port: configData.webhook.port, + bindAddress: configData.webhook.bindAddress, + }) + } + + if (this.provisioning?.port) { + this.listeners.push({ + resources: ['provisioning'], + port: this.provisioning.port, + bindAddress: this.provisioning.bindAddress, + }) + } + + if (this.metrics?.port) { + this.listeners.push({ + resources: ['metrics'], + port: this.metrics.port, + bindAddress: this.metrics.bindAddress, + }) + } + + if (this.widgets?.port) { + this.listeners.push({ + resources: ['widgets'], + port: this.widgets.port, + }) + } + } static async parseConfig(filename: string, env: {[key: string]: string|undefined}) { diff --git a/src/Config/Defaults.ts b/src/Config/Defaults.ts index 1efad140..52577cf4 100644 --- a/src/Config/Defaults.ts +++ b/src/Config/Defaults.ts @@ -21,12 +21,7 @@ export const DefaultConfig = new BridgeConfig({ level: "info", }, passFile: "passkey.pem", - webhook: { - port: 9000, - bindAddress: "0.0.0.0" - }, widgets: { - port: 5000, publicUrl: "https://example.com/bridge_widget/", addToAdminRooms: true, }, @@ -78,15 +73,23 @@ export const DefaultConfig = new BridgeConfig({ userIdPrefix: "webhooks_", }, provisioning: { - bindAddress: "127.0.0.1", - port: 9001, secret: "!secretToken" }, metrics: { enabled: true, - bindAddress: "127.0.0.1", - port: 9002, - } + }, + listeners: [ + { + port: 9000, + bindAddress: '0.0.0.0', + resources: ['webhooks', 'widgets'], + }, + { + port: 9001, + bindAddress: '127.0.0.1', + resources: ['metrics', 'provisioning'], + } + ] }, {}); function renderSection(doc: YAML.Document, obj: Record, parentNode?: YAMLSeq) { diff --git a/src/ListenerService.ts b/src/ListenerService.ts new file mode 100644 index 00000000..20f0b5d2 --- /dev/null +++ b/src/ListenerService.ts @@ -0,0 +1,71 @@ +import { Server } from "http"; +import { Application, default as expressApp, Router } from "express"; +import LogWrapper from "./LogWrapper"; + +// Appserices can't be handled yet because the bot-sdk maintains control of it. +export type ResourceName = "webhooks"|"widgets"|"metrics"|"provisioning"|"appservice"; +export const ResourceTypeArray: ResourceName[] = ["webhooks","widgets","metrics","provisioning","appservice"]; + +export interface BridgeConfigListener { + bindAddress?: string; + port: number; + resources: Array; +} + +const log = new LogWrapper("ListenerService"); + +export class ListenerService { + private readonly listeners: { + server?: Server, + app: Application, + config: BridgeConfigListener, + resourcesBound: boolean, + }[] = []; + + constructor(config: BridgeConfigListener[]) { + if (config.length < 1) { + throw Error('No listeners configured'); + } + for (const listenerConfig of config) { + const app = expressApp(); + this.listeners.push({ + config: listenerConfig, + app, + resourcesBound: false, + }); + } + } + + public bindResource(resourceName: ResourceName, router: Router) { + for (const listener of this.listeners.filter((l) => l.config.resources.includes(resourceName))) { + log.info(`Registering ${listener.config.bindAddress || "127.0.0.1"}:${listener.config.port} for ${resourceName}`); + listener.app.use(router); + listener.resourcesBound = true; + } + } + + public start() { + for (const listener of this.listeners) { + if (listener.server) { + throw Error('Cannot run start() twice'); + } + if (!listener.resourcesBound) { + continue; + } + const addr = listener.config.bindAddress || "127.0.0.1"; + listener.server = listener.app.listen(listener.config.port, addr); + log.info(`Listening on ${addr}:${listener.config.port} for ${listener.config.resources.join(', ')}`) + } + } + + public async stop() { + const promises = []; + log.info(`Stopping all listeners`); + for (const listener of this.listeners) { + if (listener.server) { + promises.push(new Promise((res, rej) => listener.server?.close((e) => e ? rej(e) : res()))); + } + } + await Promise.all(promises); + } +} diff --git a/src/Metrics.ts b/src/Metrics.ts index e3f5c07f..63e6308f 100644 --- a/src/Metrics.ts +++ b/src/Metrics.ts @@ -1,13 +1,11 @@ import { Appservice, FunctionCallContext, METRIC_MATRIX_CLIENT_FAILED_FUNCTION_CALL, METRIC_MATRIX_CLIENT_SUCCESSFUL_FUNCTION_CALL } from "matrix-bot-sdk"; import { collectDefaultMetrics, Counter, Gauge, register, Registry } from "prom-client"; -import { BridgeConfigMetrics } from "./Config/Config"; -import { Response, default as expressApp } from "express"; +import { Response, Router } from "express"; import LogWrapper from "./LogWrapper"; -import { Server } from "http"; const log = new LogWrapper("Metrics"); export class Metrics { - private httpServer?: Server; + public readonly expressRouter = Router(); public readonly webhooksHttpRequest = new Counter({ name: "hookshot_webhooks_http_request", help: "Number of requests made to the hookshot webhooks handler", labelNames: ["path", "method"], registers: [this.registry]}); public readonly provisioningHttpRequest = new Counter({ name: "hookshot_provisioning_http_request", help: "Number of requests made to the hookshot webhooks handler", labelNames: ["path", "method"], registers: [this.registry]}); @@ -24,6 +22,7 @@ export class Metrics { public readonly matrixAppserviceEvents = new Counter({ name: "matrix_appservice_events", help: "The number of events sent over the AS API", labelNames: [], registers: [this.registry]}); constructor(private registry: Registry = register) { + this.expressRouter.get('/metrics', this.metricsFunc.bind(this)); collectDefaultMetrics({ register: this.registry }) @@ -79,26 +78,6 @@ export class Metrics { res.status(500).send('Could not fetch metrics due to an error'); }); } - - public start(config: BridgeConfigMetrics, as?: Appservice) { - if (!config.port) { - if (!as) { - throw Error("No metric port defined in config, and service doesn't run a appservice"); - } - as.expressAppInstance.get('/metrics', this.metricsFunc.bind(this)); - return; - } - const app = expressApp(); - app.get('/metrics', this.metricsFunc.bind(this)); - this.httpServer = app.listen(config.port, config.bindAddress || "127.0.0.1"); - } - - public async stop() { - if (!this.httpServer) { - return; - } - return new Promise((res, rej) => this.httpServer?.close(err => err ? rej(err) : res())); - } } const singleton = new Metrics(); diff --git a/src/Webhooks.ts b/src/Webhooks.ts index ea69043d..b5a31a1f 100644 --- a/src/Webhooks.ts +++ b/src/Webhooks.ts @@ -1,5 +1,5 @@ import { BridgeConfig } from "./Config/Config"; -import { Application, default as express, Request, Response } from "express"; +import { Router, default as express, Request, Response } from "express"; import { EventEmitter } from "events"; import { MessageQueue, createMessageQueue } from "./MessageQueue"; import LogWrapper from "./LogWrapper"; @@ -37,14 +37,12 @@ export interface NotificationsDisableEvent { } export class Webhooks extends EventEmitter { - private expressApp: Application; + public readonly expressRouter = Router(); private queue: MessageQueue; - private server?: Server; private ghWebhooks?: OctokitWebhooks; constructor(private config: BridgeConfig) { super(); - this.expressApp = express(); - this.expressApp.use((req, _res, next) => { + this.expressRouter.use((req, _res, next) => { Metrics.webhooksHttpRequest.inc({path: req.path, method: req.method}); next(); }); @@ -56,39 +54,27 @@ export class Webhooks extends EventEmitter { } // TODO: Move these - this.expressApp.get("/oauth", this.onGitHubGetOauth.bind(this)); - this.expressApp.all( + this.expressRouter.get("/oauth", this.onGitHubGetOauth.bind(this)); + this.expressRouter.all( '/:hookId', express.json({ type: ['application/json', 'application/x-www-form-urlencoded'] }), this.onGenericPayload.bind(this), ); - this.expressApp.use(express.json({ + this.expressRouter.use(express.json({ verify: this.verifyRequest.bind(this), })); - this.expressApp.post("/", this.onPayload.bind(this)); + this.expressRouter.post("/", this.onPayload.bind(this)); this.queue = createMessageQueue(config); if (this.config.jira) { - this.expressApp.use("/jira", new JiraWebhooksRouter(this.config.jira, this.queue).getRouter()); + this.expressRouter.use("/jira", new JiraWebhooksRouter(this.config.jira, this.queue).getRouter()); } this.queue = createMessageQueue(config); } - public listen() { - const bindAddr = this.config.webhook.bindAddress || "0.0.0.0"; - this.server = this.expressApp.listen( - this.config.webhook.port, - bindAddr, - ); - log.info(`Listening on http://${bindAddr}:${this.config.webhook.port}`); - } - public stop() { if (this.queue.stop) { this.queue.stop(); - } - if (this.server) { - this.server.close(); } } diff --git a/src/Widgets/BridgeWidgetApi.ts b/src/Widgets/BridgeWidgetApi.ts index f8632275..073a9c60 100644 --- a/src/Widgets/BridgeWidgetApi.ts +++ b/src/Widgets/BridgeWidgetApi.ts @@ -1,4 +1,4 @@ -import { Application, default as express, Request, Response } from "express"; +import express, { Router, Request, Response } from "express"; import cors from "cors"; import { AdminRoom } from "../AdminRoom"; import LogWrapper from "../LogWrapper"; @@ -7,27 +7,19 @@ import { Server } from "http"; const log = new LogWrapper("BridgeWidgetApi"); export class BridgeWidgetApi { - private app: Application; + public readonly expressRouter: Router; private server?: Server; constructor(private adminRooms: Map) { - this.app = express(); - this.app.use((req, _res, next) => { + this.expressRouter = Router(); + this.expressRouter.use((req, _res, next) => { log.info(`${req.method} ${req.path} ${req.ip || ''} ${req.headers["user-agent"] || ''}`); next(); }); - this.app.use('/', express.static('public')); - this.app.use(cors()); - this.app.get('/widgetapi/:roomId/verify', this.getVerifyToken.bind(this)); - this.app.get('/widgetapi/:roomId', this.getRoomState.bind(this)); - this.app.get('/health', this.getHealth.bind(this)); - } - - public start(port = 5000) { - log.info(`Widget API listening on port ${port}`) - this.server = this.app.listen(port); - } - public stop() { - if (this.server) this.server.close(); + this.expressRouter.use('/', express.static('public')); + this.expressRouter.use(cors()); + this.expressRouter.get('/widgetapi/:roomId/verify', this.getVerifyToken.bind(this)); + this.expressRouter.get('/widgetapi/:roomId', this.getRoomState.bind(this)); + this.expressRouter.get('/health', this.getHealth.bind(this)); } private async getRoomFromRequest(req: Request): Promise { @@ -74,4 +66,4 @@ export class BridgeWidgetApi { private getHealth(req: Request, res: Response) { res.status(200).send({ok: true}); } -} \ No newline at end of file +} diff --git a/src/provisioning/provisioner.ts b/src/provisioning/provisioner.ts index 03cd8167..b71b5cc4 100644 --- a/src/provisioning/provisioner.ts +++ b/src/provisioning/provisioner.ts @@ -1,8 +1,7 @@ import { BridgeConfigProvisioning } from "../Config/Config"; -import { Application, default as express, NextFunction, Request, Response, Router } from "express"; +import { Router, default as express, NextFunction, Request, Response } from "express"; import { ConnectionManager } from "../ConnectionManager"; import LogWrapper from "../LogWrapper"; -import { Server } from "http"; import { ApiError, ErrCode, GetConnectionsResponseItem, GetConnectionTypeResponseItem } from "./api"; import { Intent, MembershipEventContent, PowerLevelsEventContent } from "matrix-bot-sdk"; import Metrics from "../Metrics"; @@ -15,8 +14,7 @@ const USER_ID_VALIDATOR = /@.+:.+/; export class Provisioner { - private expressApp: Application; - private server?: Server; + public readonly expressRouter: Router = Router(); constructor( private readonly config: BridgeConfigProvisioning, private readonly connMan: ConnectionManager, @@ -28,54 +26,53 @@ export class Provisioner { if (!this.config.port) { throw Error('Missing port in provisioning config'); } - this.expressApp = express(); - this.expressApp.use((req, _res, next) => { + this.expressRouter.use((req, _res, next) => { Metrics.provisioningHttpRequest.inc({path: req.path, method: req.method}); next(); }); - this.expressApp.get("/v1/health", this.getHealth); - this.expressApp.use(this.checkAuth.bind(this)); - this.expressApp.use(express.json()); + this.expressRouter.get("/v1/health", this.getHealth); + this.expressRouter.use(this.checkAuth.bind(this)); + this.expressRouter.use(express.json()); // Room Routes - this.expressApp.get( + this.expressRouter.get( "/v1/connectiontypes", this.getConnectionTypes.bind(this), ); - this.expressApp.use(this.checkUserId.bind(this)); + this.expressRouter.use(this.checkUserId.bind(this)); additionalRoutes.forEach(route => { - this.expressApp.use(route.route, route.router); + this.expressRouter.use(route.route, route.router); }); - this.expressApp.get<{roomId: string}, unknown, unknown, {userId: string}>( + this.expressRouter.get<{roomId: string}, unknown, unknown, {userId: string}>( "/v1/:roomId/connections", this.checkRoomId.bind(this), (...args) => this.checkUserPermission("read", ...args), this.getConnections.bind(this), ); - this.expressApp.get<{roomId: string, connectionId: string}, unknown, unknown, {userId: string}>( + this.expressRouter.get<{roomId: string, connectionId: string}, unknown, unknown, {userId: string}>( "/v1/:roomId/connections/:connectionId", this.checkRoomId.bind(this), (...args) => this.checkUserPermission("read", ...args), this.getConnection.bind(this), ); - this.expressApp.put<{roomId: string, type: string}, unknown, Record, {userId: string}>( + this.expressRouter.put<{roomId: string, type: string}, unknown, Record, {userId: string}>( "/v1/:roomId/connections/:type", this.checkRoomId.bind(this), (...args) => this.checkUserPermission("write", ...args), this.putConnection.bind(this), ); - this.expressApp.patch<{roomId: string, connectionId: string}, unknown, Record, {userId: string}>( + this.expressRouter.patch<{roomId: string, connectionId: string}, unknown, Record, {userId: string}>( "/v1/:roomId/connections/:connectionId", this.checkRoomId.bind(this), (...args) => this.checkUserPermission("write", ...args), this.patchConnection.bind(this), ); - this.expressApp.delete<{roomId: string, connectionId: string}, unknown, unknown, {userId: string}>( + this.expressRouter.delete<{roomId: string, connectionId: string}, unknown, unknown, {userId: string}>( "/v1/:roomId/connections/:connectionId", this.checkRoomId.bind(this), (...args) => this.checkUserPermission("write", ...args), this.deleteConnection.bind(this), ); - this.expressApp.use(this.onError); + this.expressRouter.use(this.onError); } private checkAuth(req: Request, _res: Response, next: NextFunction) { @@ -247,19 +244,4 @@ export class Provisioner { return next(ex); } } - - public listen() { - const bindAddr = this.config.bindAddress || "0.0.0.0"; - this.server = this.expressApp.listen( - this.config.port, - bindAddr, - ); - log.info(`Listening on http://${bindAddr}:${this.config.port}`); - } - - public stop() { - if (this.server) { - this.server.close(); - } - } }