From 8d6c2dfa00dd71e6087bb986ee6794aa84ebf3c0 Mon Sep 17 00:00:00 2001 From: Travis Ralston Date: Fri, 23 Mar 2018 21:01:56 -0600 Subject: [PATCH] Convert the frontend to the async/await pattern We have to drop bluebird to be able to do this. --- package.json | 1 - src/api/ApiError.ts | 4 +- src/api/dimension/DimensionAdminService.ts | 45 +++----- .../DimensionAppserviceAdminService.ts | 46 ++++---- .../DimensionIntegrationsAdminService.ts | 39 ++++--- .../dimension/DimensionIntegrationsService.ts | 49 ++++----- src/api/dimension/DimensionNebAdminService.ts | 85 +++++++-------- .../DimensionUpstreamAdminService.ts | 42 ++++--- src/api/matrix/MatrixAppServiceApiService.ts | 51 +++++---- src/api/scalar/ScalarService.ts | 103 +++++++----------- src/api/scalar/ScalarWidgetService.ts | 29 +++-- src/db/AppserviceStore.ts | 13 +-- src/db/DimensionStore.ts | 1 - src/db/NebStore.ts | 11 +- src/db/ScalarStore.ts | 5 +- src/db/WidgetStore.ts | 7 +- src/db/migrations/20171218203245-AddTables.ts | 1 - .../migrations/20171218203245-AddWidgets.ts | 1 - .../20171224122045-AddAppservices.ts | 1 - .../20171224124645-AddAppserviceUsers.ts | 1 - src/db/migrations/20171224140745-AddNeb.ts | 1 - src/matrix/MatrixAppserviceClient.ts | 1 - src/matrix/MatrixLiteClient.ts | 1 - src/matrix/MatrixOpenIdClient.ts | 1 - src/matrix/helpers.ts | 1 - src/scalar/ScalarClient.ts | 1 - 26 files changed, 243 insertions(+), 298 deletions(-) diff --git a/package.json b/package.json index 23847c1..97afd29 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,6 @@ "dependencies": { "@types/body-parser": "^1.16.8", "@types/node": "^8.9.5", - "bluebird": "^3.5.1", "body-parser": "^1.18.2", "config": "^1.30.0", "dns-then": "^0.1.0", diff --git a/src/api/ApiError.ts b/src/api/ApiError.ts index c9603a0..1df7964 100644 --- a/src/api/ApiError.ts +++ b/src/api/ApiError.ts @@ -2,8 +2,9 @@ export class ApiError { public statusCode: number; public jsonResponse: any; + public errorCode: string; - constructor(statusCode: number, json: any) { + constructor(statusCode: number, json: any, errCode = "D_UNKNOWN") { // Because typescript is just plain dumb // https://stackoverflow.com/questions/31626231/custom-error-class-in-typescript Error.apply(this, ["ApiError"]); @@ -11,5 +12,6 @@ export class ApiError { if (typeof(json) === "string") json = {message: json}; this.jsonResponse = json; this.statusCode = statusCode; + this.errorCode = errCode; } } \ No newline at end of file diff --git a/src/api/dimension/DimensionAdminService.ts b/src/api/dimension/DimensionAdminService.ts index 10f5b38..6b84905 100644 --- a/src/api/dimension/DimensionAdminService.ts +++ b/src/api/dimension/DimensionAdminService.ts @@ -1,5 +1,4 @@ import { GET, Path, QueryParam } from "typescript-rest"; -import * as Promise from "bluebird"; import { ScalarService } from "../scalar/ScalarService"; import config from "../../config"; import { ApiError } from "../ApiError"; @@ -27,51 +26,41 @@ export class DimensionAdminService { return config.admins.indexOf(userId) >= 0; } - public static validateAndGetAdminTokenOwner(scalarToken: string): Promise { - return ScalarService.getTokenOwner(scalarToken, true).then(userId => { - if (!DimensionAdminService.isAdmin(userId)) - throw new ApiError(401, {message: "You must be an administrator to use this API"}); - else return userId; - }, ScalarService.invalidTokenErrorHandler); + public static async validateAndGetAdminTokenOwner(scalarToken: string): Promise { + const userId = await ScalarService.getTokenOwner(scalarToken, true); + if (!DimensionAdminService.isAdmin(userId)) + throw new ApiError(401, "You must be an administrator to use this API"); + return userId; } @GET @Path("check") - public checkIfAdmin(@QueryParam("scalar_token") scalarToken: string): Promise<{}> { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return {}; // A 200 OK essentially means "you're an admin". - }); + public async checkIfAdmin(@QueryParam("scalar_token") scalarToken: string): Promise<{}> { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + return {}; // A 200 OK essentially means "you're an admin". } @GET @Path("version") - public getVersion(@QueryParam("scalar_token") scalarToken: string): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return {version: CURRENT_VERSION}; - }); + public async getVersion(@QueryParam("scalar_token") scalarToken: string): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + return {version: CURRENT_VERSION}; } @GET @Path("config") - public getConfig(@QueryParam("scalar_token") scalarToken: string): Promise { + public async getConfig(@QueryParam("scalar_token") scalarToken: string): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + const client = new MatrixLiteClient(config.homeserver.name, config.homeserver.accessToken); - const response: DimensionConfigResponse = { + return { admins: config.admins, widgetBlacklist: config.widgetBlacklist, homeserver: { name: config.homeserver.name, - userId: "", // populated below - federationUrl: "", // populated below + userId: await client.whoAmI(), + federationUrl: await client.getFederationUrl(), }, }; - - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return client.whoAmI(); - }).then(userId => { - response.homeserver.userId = userId; - return client.getFederationUrl(); - }).then(url => { - response.homeserver.federationUrl = url; - }).then(() => response); } } \ No newline at end of file diff --git a/src/api/dimension/DimensionAppserviceAdminService.ts b/src/api/dimension/DimensionAppserviceAdminService.ts index b253562..4467234 100644 --- a/src/api/dimension/DimensionAppserviceAdminService.ts +++ b/src/api/dimension/DimensionAppserviceAdminService.ts @@ -1,5 +1,4 @@ import { GET, Path, PathParam, POST, QueryParam } from "typescript-rest"; -import * as Promise from "bluebird"; import { DimensionAdminService } from "./DimensionAdminService"; import AppService from "../../db/models/AppService"; import { AppserviceStore } from "../../db/AppserviceStore"; @@ -33,49 +32,44 @@ export class DimensionAppserviceAdminService { @GET @Path("all") - public getAppservices(@QueryParam("scalar_token") scalarToken: string): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return AppService.findAll(); - }).then(appservices => { - return appservices.map(this.mapAppservice); - }); + public async getAppservices(@QueryParam("scalar_token") scalarToken: string): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + return (await AppService.findAll()).map(a => this.mapAppservice(a)); } @POST @Path("new") - public createAppservice(@QueryParam("scalar_token") scalarToken: string, request: AppserviceCreateRequest): Promise { + public async createAppservice(@QueryParam("scalar_token") scalarToken: string, request: AppserviceCreateRequest): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + // Trim off the @ sign if it's on the prefix if (request.userPrefix[0] === "@") { request.userPrefix = request.userPrefix.substring(1); } - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return AppserviceStore.getAllByUserPrefix(request.userPrefix); - }).then(appservices => { - if (appservices && appservices.length > 0) { - throw new ApiError(400, "User prefix is already in use"); - } + const appservices = await AppserviceStore.getAllByUserPrefix(request.userPrefix); + if (appservices && appservices.length > 0) { + throw new ApiError(400, "User prefix is already in use"); + } - return AppserviceStore.create(AppserviceStore.getSafeUserId(request.userPrefix)); - }).then(this.mapAppservice); + const appservice = await AppserviceStore.create(AppserviceStore.getSafeUserId(request.userPrefix)); + return this.mapAppservice(appservice); } @GET @Path(":appserviceId/users") - public getUsers(@QueryParam("scalar_token") scalarToken: string, @PathParam("appserviceId") asId: string): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return AppserviceStore.getUsers(asId); - }).then(users => { - return users.map(this.mapUser); - }); + public async getUsers(@QueryParam("scalar_token") scalarToken: string, @PathParam("appserviceId") asId: string): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + return (await AppserviceStore.getUsers(asId)).map(u => this.mapUser(u)); } @POST @Path(":appserviceId/users/register") - public registerUser(@QueryParam("scalar_token") scalarToken: string, @PathParam("appserviceId") asId: string, request: NewUserRequest): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return AppserviceStore.registerUser(asId, request.userId); - }).then(this.mapUser); + public async registerUser(@QueryParam("scalar_token") scalarToken: string, @PathParam("appserviceId") asId: string, request: NewUserRequest): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + + const user = await AppserviceStore.registerUser(asId, request.userId); + return this.mapUser(user); } private mapAppservice(as: AppService): AppserviceResponse { diff --git a/src/api/dimension/DimensionIntegrationsAdminService.ts b/src/api/dimension/DimensionIntegrationsAdminService.ts index 50db610..3ed77eb 100644 --- a/src/api/dimension/DimensionIntegrationsAdminService.ts +++ b/src/api/dimension/DimensionIntegrationsAdminService.ts @@ -1,10 +1,9 @@ import { GET, Path, PathParam, POST, QueryParam } from "typescript-rest"; -import * as Promise from "bluebird"; import { ApiError } from "../ApiError"; import { DimensionAdminService } from "./DimensionAdminService"; import { DimensionIntegrationsService, IntegrationsResponse } from "./DimensionIntegrationsService"; import { WidgetStore } from "../../db/WidgetStore"; -import { CACHE_INTEGRATIONS, Cache } from "../../MemoryCache"; +import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache"; interface SetEnabledRequest { enabled: boolean; @@ -19,29 +18,33 @@ export class DimensionIntegrationsAdminService { @POST @Path(":category/:type/options") - public setOptions(@QueryParam("scalar_token") scalarToken: string, @PathParam("category") category: string, @PathParam("type") type: string, body: SetOptionsRequest): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - if (category === "widget") { - return WidgetStore.setOptions(type, body.options); - } else throw new ApiError(400, "Unrecongized category"); - }).then(() => Cache.for(CACHE_INTEGRATIONS).clear()); + public async setOptions(@QueryParam("scalar_token") scalarToken: string, @PathParam("category") category: string, @PathParam("type") type: string, body: SetOptionsRequest): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + + if (category === "widget") await WidgetStore.setOptions(type, body.options); + else throw new ApiError(400, "Unrecognized category"); + + Cache.for(CACHE_INTEGRATIONS).clear(); + return {}; // 200 OK } + @POST @Path(":category/:type/enabled") - public setEnabled(@QueryParam("scalar_token") scalarToken: string, @PathParam("category") category: string, @PathParam("type") type: string, body: SetEnabledRequest): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - if (category === "widget") { - return WidgetStore.setEnabled(type, body.enabled); - } else throw new ApiError(400, "Unrecongized category"); - }).then(() => Cache.for(CACHE_INTEGRATIONS).clear()); + public async setEnabled(@QueryParam("scalar_token") scalarToken: string, @PathParam("category") category: string, @PathParam("type") type: string, body: SetEnabledRequest): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + + if (category === "widget") await WidgetStore.setEnabled(type, body.enabled); + else throw new ApiError(400, "Unrecognized category"); + + Cache.for(CACHE_INTEGRATIONS).clear(); + return {}; // 200 OK } @GET @Path("all") - public getAllIntegrations(@QueryParam("scalar_token") scalarToken: string): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return DimensionIntegrationsService.getIntegrations(null); - }); + public async getAllIntegrations(@QueryParam("scalar_token") scalarToken: string): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + return DimensionIntegrationsService.getIntegrations(null); } } \ No newline at end of file diff --git a/src/api/dimension/DimensionIntegrationsService.ts b/src/api/dimension/DimensionIntegrationsService.ts index fd6617f..728852a 100644 --- a/src/api/dimension/DimensionIntegrationsService.ts +++ b/src/api/dimension/DimensionIntegrationsService.ts @@ -1,8 +1,7 @@ import { GET, Path, PathParam, QueryParam } from "typescript-rest"; -import * as Promise from "bluebird"; import { ScalarService } from "../scalar/ScalarService"; import { Widget } from "../../integrations/Widget"; -import { CACHE_INTEGRATIONS, Cache } from "../../MemoryCache"; +import { Cache, CACHE_INTEGRATIONS } from "../../MemoryCache"; import { Integration } from "../../integrations/Integration"; import { ApiError } from "../ApiError"; import { WidgetStore } from "../../db/WidgetStore"; @@ -14,34 +13,27 @@ export interface IntegrationsResponse { @Path("/api/v1/dimension/integrations") export class DimensionIntegrationsService { - public static getIntegrations(isEnabledCheck?: boolean): Promise { - const cachedResponse = Cache.for(CACHE_INTEGRATIONS).get("integrations_" + isEnabledCheck); - if (cachedResponse) { - return cachedResponse; + public static async getIntegrations(isEnabledCheck?: boolean): Promise { + const cachedWidgets = Cache.for(CACHE_INTEGRATIONS).get("integrations_" + isEnabledCheck); + if (cachedWidgets) { + return {widgets: cachedWidgets}; } - const response = { - widgets: [], - }; - return Promise.resolve() - .then(() => WidgetStore.listAll(isEnabledCheck)) - .then(widgets => response.widgets = widgets) - // Cache and return response - .then(() => Cache.for(CACHE_INTEGRATIONS).put("integrations_" + isEnabledCheck, response)) - .then(() => response); + const widgets = await WidgetStore.listAll(isEnabledCheck); + Cache.for(CACHE_INTEGRATIONS).put("integrations_" + isEnabledCheck, widgets); + return {widgets: widgets}; } @GET @Path("enabled") - public getEnabledIntegrations(@QueryParam("scalar_token") scalarToken: string): Promise { - return ScalarService.getTokenOwner(scalarToken).then(_userId => { - return DimensionIntegrationsService.getIntegrations(true); - }, ScalarService.invalidTokenErrorHandler); + public async getEnabledIntegrations(@QueryParam("scalar_token") scalarToken: string): Promise { + await ScalarService.getTokenOwner(scalarToken); + return DimensionIntegrationsService.getIntegrations(true); } @GET @Path("room/:roomId") - public getIntegrationsInRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string): Promise { + public async getIntegrationsInRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string): Promise { console.log(roomId); // TODO: Other integrations return this.getEnabledIntegrations(scalarToken); @@ -49,18 +41,15 @@ export class DimensionIntegrationsService { @GET @Path(":category/:type") - public getIntegration(@PathParam("category") category: string, @PathParam("type") type: string): Promise { + public async getIntegration(@PathParam("category") category: string, @PathParam("type") type: string): Promise { // This is intentionally an unauthed endpoint to ensure we can use it in widgets - return DimensionIntegrationsService.getIntegrations(true).then(response => { - for (const key of Object.keys(response)) { - for (const integration of response[key]) { - if (integration.category === category && integration.type === type) { - return integration; - } - } + const integrationsResponse = await DimensionIntegrationsService.getIntegrations(true); + for (const key in integrationsResponse) { + for (const integration of integrationsResponse[key]) { + if (integration.category === category && integration.type === type) return integration; } + } - throw new ApiError(404, "Integration not found"); - }); + throw new ApiError(404, "Integration not found"); } } \ No newline at end of file diff --git a/src/api/dimension/DimensionNebAdminService.ts b/src/api/dimension/DimensionNebAdminService.ts index eeebc2b..aefa6e1 100644 --- a/src/api/dimension/DimensionNebAdminService.ts +++ b/src/api/dimension/DimensionNebAdminService.ts @@ -1,5 +1,4 @@ import { GET, Path, PathParam, POST, QueryParam } from "typescript-rest"; -import * as Promise from "bluebird"; import { DimensionAdminService } from "./DimensionAdminService"; import { Cache, CACHE_NEB } from "../../MemoryCache"; import { NebStore } from "../../db/NebStore"; @@ -26,66 +25,66 @@ export class DimensionNebAdminService { @GET @Path("all") - public getNebConfigs(@QueryParam("scalar_token") scalarToken: string): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - const cachedConfigs = Cache.for(CACHE_NEB).get("configurations"); - if (cachedConfigs) return cachedConfigs; + public async getNebConfigs(@QueryParam("scalar_token") scalarToken: string): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); - return NebStore.getAllConfigs().then(configs => { - Cache.for(CACHE_NEB).put("configurations", configs); - return configs; - }); - }); + const cachedConfigs = Cache.for(CACHE_NEB).get("configurations"); + if (cachedConfigs) return cachedConfigs; + + const configs = await NebStore.getAllConfigs(); + Cache.for(CACHE_NEB).put("configurations", configs); + return configs; } @GET @Path(":id/config") - public getNebConfig(@QueryParam("scalar_token") scalarToken: string, @PathParam("id") nebId: number): Promise { - return this.getNebConfigs(scalarToken).then(configs => { - for (const config of configs) { - if (config.id === nebId) return config; - } - - throw new ApiError(404, "Configuration not found"); - }); + public async getNebConfig(@QueryParam("scalar_token") scalarToken: string, @PathParam("id") nebId: number): Promise { + const configs = await this.getNebConfigs(scalarToken); // does auth for us + const firstConfig = configs.filter(c => c.id === nebId)[0]; + if (!firstConfig) throw new ApiError(404, "Configuration not found"); + return firstConfig; } @POST @Path(":id/integration/:type/enabled") - public setIntegrationEnabled(@QueryParam("scalar_token") scalarToken: string, @PathParam("id") nebId: number, @PathParam("type") integrationType: string, request: SetEnabledRequest): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return NebStore.getOrCreateIntegration(nebId, integrationType); - }).then(integration => { - integration.isEnabled = request.enabled; - return integration.save(); - }).then(() => Cache.for(CACHE_NEB).clear()); + public async setIntegrationEnabled(@QueryParam("scalar_token") scalarToken: string, @PathParam("id") nebId: number, @PathParam("type") integrationType: string, request: SetEnabledRequest): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + + const integration = await NebStore.getOrCreateIntegration(nebId, integrationType); + integration.isEnabled = request.enabled; + await integration.save(); + Cache.for(CACHE_NEB).clear(); + + return {}; // 200 OK } @POST @Path("new/upstream") - public newConfigForUpstream(@QueryParam("scalar_token") scalarToken: string, request: CreateWithUpstream): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return NebStore.createForUpstream(request.upstreamId).catch(err => { - LogService.error("DimensionNebAdminService", err); - throw new ApiError(500, "Error creating go-neb instance"); - }); - }).then(config => { + public async newConfigForUpstream(@QueryParam("scalar_token") scalarToken: string, request: CreateWithUpstream): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + + try { + const neb = await NebStore.createForUpstream(request.upstreamId); Cache.for(CACHE_NEB).clear(); - return config; - }); + return neb; + } catch (err) { + LogService.error("DimensionNebAdminService", err); + throw new ApiError(500, "Error creating go-neb instance"); + } } @POST @Path("new/appservice") - public newConfigForAppservice(@QueryParam("scalar_token") scalarToken: string, request: CreateWithAppservice): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return NebStore.createForAppservice(request.appserviceId, request.adminUrl).catch(err => { - LogService.error("DimensionNebAdminService", err); - throw new ApiError(500, "Error creating go-neb instance"); - }); - }).then(config => { + public async newConfigForAppservice(@QueryParam("scalar_token") scalarToken: string, request: CreateWithAppservice): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + + try { + const neb = await NebStore.createForAppservice(request.appserviceId, request.adminUrl); Cache.for(CACHE_NEB).clear(); - return config; - }); + return neb; + } catch (err) { + LogService.error("DimensionNebAdminService", err); + throw new ApiError(500, "Error creating go-neb instance"); + } } } \ No newline at end of file diff --git a/src/api/dimension/DimensionUpstreamAdminService.ts b/src/api/dimension/DimensionUpstreamAdminService.ts index bc8fc6a..74408ca 100644 --- a/src/api/dimension/DimensionUpstreamAdminService.ts +++ b/src/api/dimension/DimensionUpstreamAdminService.ts @@ -1,5 +1,4 @@ import { GET, Path, POST, QueryParam } from "typescript-rest"; -import * as Promise from "bluebird"; import { DimensionAdminService } from "./DimensionAdminService"; import { Cache, CACHE_UPSTREAM } from "../../MemoryCache"; import Upstream from "../../db/models/Upstream"; @@ -24,33 +23,32 @@ export class DimensionUpstreamAdminService { @GET @Path("all") - public getUpstreams(@QueryParam("scalar_token") scalarToken: string): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - const cachedUpstreams = Cache.for(CACHE_UPSTREAM).get("upstreams"); - if (cachedUpstreams) return cachedUpstreams; - return Upstream.findAll().then(upstreams => { - const mapped = upstreams.map(this.mapUpstream); - Cache.for(CACHE_UPSTREAM).put("upstreams", mapped); + public async getUpstreams(@QueryParam("scalar_token") scalarToken: string): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); - return mapped; - }); - }); + const cachedUpstreams = Cache.for(CACHE_UPSTREAM).get("upstreams"); + if (cachedUpstreams) return cachedUpstreams; + + const upstreams = await Upstream.findAll(); + const mapped = upstreams.map(u => this.mapUpstream(u)); + Cache.for(CACHE_UPSTREAM).put("upstreams", mapped); + return mapped; } @POST @Path("new") - public createUpstream(@QueryParam("scalar_token") scalarToken: string, request: NewUpstreamRequest): Promise { - return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => { - return Upstream.create({ - name: request.name, - type: request.type, - scalarUrl: request.scalarUrl, - apiUrl: request.apiUrl, - }); - }).then(upstream => { - Cache.for(CACHE_UPSTREAM).clear(); - return this.mapUpstream(upstream); + public async createUpstream(@QueryParam("scalar_token") scalarToken: string, request: NewUpstreamRequest): Promise { + await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken); + + const upstream = await Upstream.create({ + name: request.name, + type: request.type, + scalarUrl: request.scalarUrl, + apiUrl: request.apiUrl, }); + + Cache.for(CACHE_UPSTREAM).clear(); + return this.mapUpstream(upstream); } private mapUpstream(upstream: Upstream): UpstreamRepsonse { diff --git a/src/api/matrix/MatrixAppServiceApiService.ts b/src/api/matrix/MatrixAppServiceApiService.ts index 505ad63..417b121 100644 --- a/src/api/matrix/MatrixAppServiceApiService.ts +++ b/src/api/matrix/MatrixAppServiceApiService.ts @@ -1,5 +1,4 @@ import { GET, Path, PathParam, PUT, QueryParam } from "typescript-rest"; -import * as Promise from "bluebird"; import { ApiError } from "../ApiError"; import { LogService } from "matrix-js-snippets"; import { SimplifiedMatrixEvent } from "../../models/MatrixEvent"; @@ -18,48 +17,58 @@ export class MatrixAppServiceApiService { @PUT @Path("/transactions/:txnId") - public onTransaction(@QueryParam("access_token") homeserverToken: string, @PathParam("txnId") txnId: string, _txn: AppServiceTransaction): Promise { - return AppserviceStore.getByHomeserverToken(homeserverToken).then(appservice => { + public async onTransaction(@QueryParam("access_token") homeserverToken: string, @PathParam("txnId") txnId: string, _txn: AppServiceTransaction): Promise { + try { + const appservice = await AppserviceStore.getByHomeserverToken(homeserverToken); + // We don't handle the transaction at all - we just don't want the homeserver to consider us down LogService.verbose("MatrixAppServiceApiService", "Accepting transaction " + txnId + " for appservice " + appservice.id + " blindly"); return {}; // 200 OK - }, err => { + } catch (err) { LogService.error("MatrixAppServiceApiService", err); throw new ApiError(403, {errcode: "M_FORBIDDEN"}); - }); + } } @GET @Path("/room/:alias") - public getRoom(@QueryParam("access_token") homeserverToken: string, @PathParam("alias") roomAlias: string): Promise { - return AppserviceStore.getByHomeserverToken(homeserverToken).then(appservice => { + public async getRoom(@QueryParam("access_token") homeserverToken: string, @PathParam("alias") roomAlias: string): Promise { + try { + const appservice = await AppserviceStore.getByHomeserverToken(homeserverToken); + // We don't support room lookups LogService.verbose("MatrixAppServiceApiService", "404ing request for room " + roomAlias + " at appservice " + appservice.id); throw new ApiError(404, {errcode: "IO.T2BOT.DIMENSION.ROOMS_NOT_SUPPORTED"}); - }, err => { + } catch (err) { + if (err instanceof ApiError) throw err; LogService.error("MatrixAppServiceApiService", err); throw new ApiError(403, {errcode: "M_FORBIDDEN"}); - }); + } } @GET @Path("/user/:userId") - public getUser(@QueryParam("access_token") homeserverToken: string, @PathParam("userId") userId: string): Promise { - return AppserviceStore.getByHomeserverToken(homeserverToken).then(appservice => { - return AppserviceStore.getUser(appservice.id, userId).catch(err => { + public async getUser(@QueryParam("access_token") homeserverToken: string, @PathParam("userId") userId: string): Promise { + try { + const appservice = await AppserviceStore.getByHomeserverToken(homeserverToken); + + try { + const user = await AppserviceStore.getUser(appservice.id, userId); + return { + userId: user.id, + displayName: user.displayName, + avatarUrl: user.avatarUrl, + } + } catch (err) { LogService.error("MatrixAppServiceApiService", err); throw new ApiError(404, {errcode: "IO.T2BOT.DIMENSION.USER_NOT_FOUND"}); - }); - }, err => { + } + } catch (err) { + if (err instanceof ApiError) throw err; + LogService.error("MatrixAppServiceApiService", err); throw new ApiError(403, {errcode: "M_FORBIDDEN"}); - }).then(appserviceUser => { - return { - userId: appserviceUser.id, - displayName: appserviceUser.displayName, - avatarUrl: appserviceUser.avatarUrl, - }; - }); + } } } \ No newline at end of file diff --git a/src/api/scalar/ScalarService.ts b/src/api/scalar/ScalarService.ts index 3d7c467..758aa6a 100644 --- a/src/api/scalar/ScalarService.ts +++ b/src/api/scalar/ScalarService.ts @@ -1,5 +1,4 @@ import { GET, Path, POST, QueryParam } from "typescript-rest"; -import * as Promise from "bluebird"; import { MatrixOpenIdClient } from "../../matrix/MatrixOpenIdClient"; import Upstream from "../../db/models/Upstream"; import { ScalarClient } from "../../scalar/ScalarClient"; @@ -23,80 +22,62 @@ interface RegisterRequest { @Path("/api/v1/scalar") export class ScalarService { - public static getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise { + public static async getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise { const cachedUserId = Cache.for(CACHE_SCALAR_ACCOUNTS).get(scalarToken); - if (cachedUserId) return Promise.resolve(cachedUserId); + if (cachedUserId) return cachedUserId; - return ScalarStore.getTokenOwner(scalarToken, ignoreUpstreams).then(user => { - if (!user) return Promise.reject("Invalid token"); - Cache.for(CACHE_SCALAR_ACCOUNTS).put(scalarToken, user.userId, 30 * 60 * 1000); // 30 minutes - return Promise.resolve(user.userId); - }); - } + const user = await ScalarStore.getTokenOwner(scalarToken, ignoreUpstreams); + if (!user) throw new ApiError(401, "Invalid token"); - public static invalidTokenErrorHandler(error: any): any { - if (error !== "Invalid token") { - LogService.error("ScalarWidgetService", "Error processing request"); - LogService.error("ScalarWidgetService", error); - } - throw new ApiError(401, {message: "Invalid token"}); + Cache.for(CACHE_SCALAR_ACCOUNTS).put(scalarToken, user.userId, 30 * 60 * 1000); // 30 minutes + return user.userId; } @POST @Path("register") - public register(request: RegisterRequest): Promise { - let userId = null; + public async register(request: RegisterRequest): Promise { const mxClient = new MatrixOpenIdClient(request); - return mxClient.getUserId().then(mxUserId => { - userId = mxUserId; - return User.findByPrimary(userId).then(user => { - if (!user) { - // There's a small chance we'll get a validation error because of: - // https://github.com/vector-im/riot-web/issues/5846 - LogService.verbose("ScalarService", "User " + userId + " never seen before - creating"); - return User.create({userId: userId}); - } - }); - }).then(() => { - return Upstream.findAll(); - }).then(upstreams => { - return Promise.all(upstreams.map(u => { - return UserScalarToken.findAll({where: {userId: userId, upstreamId: u.id}}).then(tokens => { - if (!tokens || tokens.length === 0) { - LogService.info("ScalarService", "Registering " + userId + " for token at upstream " + u.id + " (" + u.name + ")"); - const client = new ScalarClient(u); - return client.register(request).then(registerResponse => { - return UserScalarToken.create({ - userId: userId, - scalarToken: registerResponse.scalar_token, - isDimensionToken: false, - upstreamId: u.id, - }); - }); - } + const mxUserId = await mxClient.getUserId(); + + const user = await User.findByPrimary(mxUserId); + if (!user) { + // There's a small chance we'll get a validation error because of: + // https://github.com/vector-im/riot-web/issues/5846 + LogService.verbose("ScalarService", "User " + mxUserId + " never seen before - creating"); + await User.create({userId: mxUserId}); + } + + const upstreams = await Upstream.findAll(); + await Promise.all(upstreams.map(async upstream => { + const tokens = await UserScalarToken.findAll({where: {userId: mxUserId, upstreamId: upstream.id}}); + if (!tokens || tokens.length === 0) { + LogService.info("ScalarService", "Registering " + mxUserId + " for a token at upstream " + upstream.id + " (" + upstream.name + ")"); + const client = new ScalarClient(upstream); + const response = await client.register(request); + return UserScalarToken.create({ + userId: mxUserId, + scalarToken: response.scalar_token, + isDimensionToken: false, + upstreamId: upstream.id, }); - })); - }).then(() => { - const dimensionToken = randomString({length: 25}); - return UserScalarToken.create({ - userId: userId, - scalarToken: dimensionToken, - isDimensionToken: true, - }); - }).then(userToken => { - return {scalar_token: userToken.scalarToken}; - }).catch(err => { - LogService.error("ScalarService", err); - throw new ApiError(401, {message: "Failed to authenticate user"}); + } + })); + + const dimensionToken = randomString({length: 25}); + const dimensionScalarToken = await UserScalarToken.create({ + userId: mxUserId, + scalarToken: dimensionToken, + isDimensionToken: true, }); + + return {scalar_token: dimensionScalarToken.scalarToken}; } @GET @Path("account") - public getAccount(@QueryParam("scalar_token") scalarToken: string): Promise { - return ScalarService.getTokenOwner(scalarToken).then(userId => { - return {user_id: userId}; - }, ScalarService.invalidTokenErrorHandler); + public async getAccount(@QueryParam("scalar_token") scalarToken: string): Promise { + const userId = await ScalarService.getTokenOwner(scalarToken); + return {user_id: userId}; } } \ No newline at end of file diff --git a/src/api/scalar/ScalarWidgetService.ts b/src/api/scalar/ScalarWidgetService.ts index 46e40b8..dfca227 100644 --- a/src/api/scalar/ScalarWidgetService.ts +++ b/src/api/scalar/ScalarWidgetService.ts @@ -1,7 +1,6 @@ import { GET, Path, QueryParam } from "typescript-rest"; -import * as Promise from "bluebird"; import { LogService } from "matrix-js-snippets"; -import { CACHE_WIDGET_TITLES, Cache } from "../../MemoryCache"; +import { Cache, CACHE_WIDGET_TITLES } from "../../MemoryCache"; import { MatrixLiteClient } from "../../matrix/MatrixLiteClient"; import config from "../../config"; import { ScalarService } from "./ScalarService"; @@ -22,15 +21,21 @@ interface UrlPreviewResponse { @Path("/api/v1/scalar/widgets") export class ScalarWidgetService { - private static getUrlTitle(url: string): Promise { + @GET + @Path("title_lookup") + public async titleLookup(@QueryParam("scalar_token") scalarToken: string, @QueryParam("curl") url: string): Promise { + await ScalarService.getTokenOwner(scalarToken); + const cachedResult = Cache.for(CACHE_WIDGET_TITLES).get(url); if (cachedResult) { cachedResult.cached_response = true; - return Promise.resolve(cachedResult); + return cachedResult; } const client = new MatrixLiteClient(config.homeserver.name, config.homeserver.accessToken); - return client.getUrlPreview(url).then(preview => { + + try { + const preview = await client.getUrlPreview(url); const expirationTime = 60 * 80 * 1000; // 1 hour const expirationAsString = moment().add(expirationTime, "milliseconds").toISOString(); const cachedItem = { @@ -44,7 +49,7 @@ export class ScalarWidgetService { }; Cache.for(CACHE_WIDGET_TITLES).put(url, cachedItem, expirationTime); return cachedItem; - }).catch(err => { + } catch (err) { LogService.error("ScalarWidgetService", "Error getting URL preview"); LogService.error("ScalarWidgetService", err); return { @@ -53,20 +58,12 @@ export class ScalarWidgetService { page_title_cache_item: { expires: null, cached_response_err: "Failed to get URL preview", - cached_title: null + cached_title: null, }, error: { message: "Failed to get URL preview", }, }; - }) - } - - @GET - @Path("title_lookup") - public titleLookup(@QueryParam("scalar_token") scalarToken: string, @QueryParam("curl") url: string): Promise { - return ScalarService.getTokenOwner(scalarToken).then(_userId => { - return ScalarWidgetService.getUrlTitle(url); - }, ScalarService.invalidTokenErrorHandler); + } } } \ No newline at end of file diff --git a/src/db/AppserviceStore.ts b/src/db/AppserviceStore.ts index c454270..fce1b91 100644 --- a/src/db/AppserviceStore.ts +++ b/src/db/AppserviceStore.ts @@ -1,14 +1,13 @@ import AppService from "./models/AppService"; import AppServiceUser from "./models/AppServiceUser"; import * as randomString from "random-string"; -import * as Promise from "bluebird"; import { MatrixAppserviceClient } from "../matrix/MatrixAppserviceClient"; import { resolveIfExists } from "./DimensionStore"; import config from "../config"; export class AppserviceStore { - public static create(userPrefix: string): Promise { + public static async create(userPrefix: string): Promise { const id = "dimension-" + randomString({length: 25}); const asToken = randomString({length: 100}); const hsToken = randomString({length: 100}); @@ -21,15 +20,15 @@ export class AppserviceStore { }); } - public static getUser(appserviceId: string, userId: string): Promise { + public static async getUser(appserviceId: string, userId: string): Promise { return AppServiceUser.findOne({where: {appserviceId: appserviceId, id: userId}}).then(resolveIfExists); } - public static getByHomeserverToken(hsToken: string): Promise { + public static async getByHomeserverToken(hsToken: string): Promise { return AppService.findOne({where: {hsToken: hsToken}}).then(resolveIfExists); } - public static getAllByUserPrefix(userPrefix: string): Promise { + public static async getAllByUserPrefix(userPrefix: string): Promise { return AppService.findAll({where: {userPrefix: userPrefix}}); } @@ -39,11 +38,11 @@ export class AppserviceStore { return userIdOrPrefix.toLowerCase().replace(/[^a-z0-9._\-=]/g, '.'); } - public static getUsers(appserviceId: string): Promise { + public static async getUsers(appserviceId: string): Promise { return AppServiceUser.findAll({where: {appserviceId: appserviceId}}); } - public static registerUser(appserviceId: string, userId: string): Promise { + public static async registerUser(appserviceId: string, userId: string): Promise { userId = AppserviceStore.getSafeUserId(userId); return AppService.findOne({where: {id: appserviceId}}).then(resolveIfExists).then(appservice => { const client = new MatrixAppserviceClient(config.homeserver.name, appservice); diff --git a/src/db/DimensionStore.ts b/src/db/DimensionStore.ts index 34e7d2a..ecf05a3 100644 --- a/src/db/DimensionStore.ts +++ b/src/db/DimensionStore.ts @@ -4,7 +4,6 @@ import { LogService } from "matrix-js-snippets"; import User from "./models/User"; import UserScalarToken from "./models/UserScalarToken"; import Upstream from "./models/Upstream"; -import * as Promise from "bluebird"; import WidgetRecord from "./models/WidgetRecord"; import * as path from "path"; import * as Umzug from "umzug"; diff --git a/src/db/NebStore.ts b/src/db/NebStore.ts index faf8849..0df0406 100644 --- a/src/db/NebStore.ts +++ b/src/db/NebStore.ts @@ -1,4 +1,3 @@ -import * as Promise from "bluebird"; import { resolveIfExists } from "./DimensionStore"; import { NebConfig } from "../models/neb"; import NebConfiguration from "./models/NebConfiguration"; @@ -77,13 +76,13 @@ export class NebStore { }, }; - public static getAllConfigs(): Promise { + public static async getAllConfigs(): Promise { return NebConfiguration.findAll().then(configs => { return Promise.all((configs || []).map(c => NebStore.getConfig(c.id))); }); } - public static getConfig(id: number): Promise { + public static async getConfig(id: number): Promise { let nebConfig: NebConfiguration; return NebConfiguration.findByPrimary(id).then(resolveIfExists).then(conf => { nebConfig = conf; @@ -95,7 +94,7 @@ export class NebStore { }); } - public static createForUpstream(upstreamId: number): Promise { + public static async createForUpstream(upstreamId: number): Promise { return Upstream.findByPrimary(upstreamId).then(resolveIfExists).then(upstream => { return NebConfiguration.create({ upstreamId: upstream.id, @@ -105,7 +104,7 @@ export class NebStore { }); } - public static createForAppservice(appserviceId: string, adminUrl: string): Promise { + public static async createForAppservice(appserviceId: string, adminUrl: string): Promise { return AppService.findByPrimary(appserviceId).then(resolveIfExists).then(appservice => { return NebConfiguration.create({ appserviceId: appservice.id, @@ -116,7 +115,7 @@ export class NebStore { }); } - public static getOrCreateIntegration(configurationId: number, integrationType: string): Promise { + public static async getOrCreateIntegration(configurationId: number, integrationType: string): Promise { if (!NebStore.INTEGRATIONS[integrationType]) return Promise.reject(new Error("Integration not supported")); return NebConfiguration.findByPrimary(configurationId).then(resolveIfExists).then(config => { diff --git a/src/db/ScalarStore.ts b/src/db/ScalarStore.ts index b34c5ea..99bb37a 100644 --- a/src/db/ScalarStore.ts +++ b/src/db/ScalarStore.ts @@ -1,4 +1,3 @@ -import * as Promise from "bluebird"; import UserScalarToken from "./models/UserScalarToken"; import { LogService } from "matrix-js-snippets"; import Upstream from "./models/Upstream"; @@ -6,7 +5,7 @@ import User from "./models/User"; export class ScalarStore { - public static doesUserHaveTokensForAllUpstreams(userId: string): Promise { + public static async doesUserHaveTokensForAllUpstreams(userId: string): Promise { let upstreamTokenIds: number[] = []; let hasDimensionToken = false; return UserScalarToken.findAll({where: {userId: userId}}).then(results => { @@ -30,7 +29,7 @@ export class ScalarStore { }); } - public static getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise { + public static async getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise { let user: User = null; return UserScalarToken.findAll({ where: {isDimensionToken: true, scalarToken: scalarToken}, diff --git a/src/db/WidgetStore.ts b/src/db/WidgetStore.ts index 4c22a5b..4a00ea8 100644 --- a/src/db/WidgetStore.ts +++ b/src/db/WidgetStore.ts @@ -1,24 +1,23 @@ -import * as Promise from "bluebird"; import WidgetRecord from "./models/WidgetRecord"; import { Widget } from "../integrations/Widget"; import { resolveIfExists } from "./DimensionStore"; export class WidgetStore { - public static listAll(isEnabled?: boolean): Promise { + public static async listAll(isEnabled?: boolean): Promise { let conditions = {}; if (isEnabled === true || isEnabled === false) conditions = {where: {isEnabled: isEnabled}}; return WidgetRecord.findAll(conditions).then(widgets => widgets.map(w => new Widget(w))); } - public static setEnabled(type: string, isEnabled: boolean): Promise { + public static async setEnabled(type: string, isEnabled: boolean): Promise { return WidgetRecord.findOne({where: {type: type}}).then(resolveIfExists).then(widget => { widget.isEnabled = isEnabled; return widget.save(); }); } - public static setOptions(type: string, options: any): Promise { + public static async setOptions(type: string, options: any): Promise { const optionsJson = JSON.stringify(options); return WidgetRecord.findOne({where: {type: type}}).then(resolveIfExists).then(widget => { widget.optionsJson = optionsJson; diff --git a/src/db/migrations/20171218203245-AddTables.ts b/src/db/migrations/20171218203245-AddTables.ts index 2bb1847..d967582 100644 --- a/src/db/migrations/20171218203245-AddTables.ts +++ b/src/db/migrations/20171218203245-AddTables.ts @@ -1,6 +1,5 @@ import { QueryInterface } from "sequelize"; import { DataType } from "sequelize-typescript"; -import * as Promise from "bluebird"; export default { up: (queryInterface: QueryInterface) => { diff --git a/src/db/migrations/20171218203245-AddWidgets.ts b/src/db/migrations/20171218203245-AddWidgets.ts index 7ad07fa..19b51c7 100644 --- a/src/db/migrations/20171218203245-AddWidgets.ts +++ b/src/db/migrations/20171218203245-AddWidgets.ts @@ -1,6 +1,5 @@ import { QueryInterface } from "sequelize"; import { DataType } from "sequelize-typescript"; -import * as Promise from "bluebird"; export default { up: (queryInterface: QueryInterface) => { diff --git a/src/db/migrations/20171224122045-AddAppservices.ts b/src/db/migrations/20171224122045-AddAppservices.ts index cb22d08..d64f5b0 100644 --- a/src/db/migrations/20171224122045-AddAppservices.ts +++ b/src/db/migrations/20171224122045-AddAppservices.ts @@ -1,6 +1,5 @@ import { QueryInterface } from "sequelize"; import { DataType } from "sequelize-typescript"; -import * as Promise from "bluebird"; export default { up: (queryInterface: QueryInterface) => { diff --git a/src/db/migrations/20171224124645-AddAppserviceUsers.ts b/src/db/migrations/20171224124645-AddAppserviceUsers.ts index 914d9db..4ae1877 100644 --- a/src/db/migrations/20171224124645-AddAppserviceUsers.ts +++ b/src/db/migrations/20171224124645-AddAppserviceUsers.ts @@ -1,6 +1,5 @@ import { QueryInterface } from "sequelize"; import { DataType } from "sequelize-typescript"; -import * as Promise from "bluebird"; export default { up: (queryInterface: QueryInterface) => { diff --git a/src/db/migrations/20171224140745-AddNeb.ts b/src/db/migrations/20171224140745-AddNeb.ts index 4fcf944..42d1720 100644 --- a/src/db/migrations/20171224140745-AddNeb.ts +++ b/src/db/migrations/20171224140745-AddNeb.ts @@ -1,6 +1,5 @@ import { QueryInterface } from "sequelize"; import { DataType } from "sequelize-typescript"; -import * as Promise from "bluebird"; export default { up: (queryInterface: QueryInterface) => { diff --git a/src/matrix/MatrixAppserviceClient.ts b/src/matrix/MatrixAppserviceClient.ts index 2d79560..4afefbc 100644 --- a/src/matrix/MatrixAppserviceClient.ts +++ b/src/matrix/MatrixAppserviceClient.ts @@ -1,4 +1,3 @@ -import * as Promise from "bluebird"; import { doFederatedApiCall } from "./helpers"; import AppService from "../db/models/AppService"; diff --git a/src/matrix/MatrixLiteClient.ts b/src/matrix/MatrixLiteClient.ts index faf8782..e13958a 100644 --- a/src/matrix/MatrixLiteClient.ts +++ b/src/matrix/MatrixLiteClient.ts @@ -1,4 +1,3 @@ -import * as Promise from "bluebird"; import { doFederatedApiCall, getFederationUrl as getFedUrl } from "./helpers"; export interface MatrixUrlPreview { diff --git a/src/matrix/MatrixOpenIdClient.ts b/src/matrix/MatrixOpenIdClient.ts index beec738..7756083 100644 --- a/src/matrix/MatrixOpenIdClient.ts +++ b/src/matrix/MatrixOpenIdClient.ts @@ -1,4 +1,3 @@ -import * as Promise from "bluebird"; import { doFederatedApiCall } from "./helpers"; import { OpenId } from "../models/OpenId"; diff --git a/src/matrix/helpers.ts b/src/matrix/helpers.ts index 4700890..2b3d2d2 100644 --- a/src/matrix/helpers.ts +++ b/src/matrix/helpers.ts @@ -1,5 +1,4 @@ import * as dns from "dns-then"; -import * as Promise from "bluebird"; import { LogService } from "matrix-js-snippets"; import { Cache, CACHE_FEDERATION } from "../MemoryCache"; import * as request from "request"; diff --git a/src/scalar/ScalarClient.ts b/src/scalar/ScalarClient.ts index f064ab7..4cc86b6 100644 --- a/src/scalar/ScalarClient.ts +++ b/src/scalar/ScalarClient.ts @@ -1,6 +1,5 @@ import { OpenId } from "../models/OpenId"; import { ScalarRegisterResponse } from "../models/ScalarResponses"; -import * as Promise from "bluebird"; import * as request from "request"; import { LogService } from "matrix-js-snippets"; import Upstream from "../db/models/Upstream";