diff --git a/src/api/scalar/ScalarService.ts b/src/api/scalar/ScalarService.ts index 758aa6a..1bc429b 100644 --- a/src/api/scalar/ScalarService.ts +++ b/src/api/scalar/ScalarService.ts @@ -26,11 +26,14 @@ export class ScalarService { const cachedUserId = Cache.for(CACHE_SCALAR_ACCOUNTS).get(scalarToken); if (cachedUserId) return cachedUserId; - const user = await ScalarStore.getTokenOwner(scalarToken, ignoreUpstreams); - if (!user) throw new ApiError(401, "Invalid token"); - - Cache.for(CACHE_SCALAR_ACCOUNTS).put(scalarToken, user.userId, 30 * 60 * 1000); // 30 minutes - return user.userId; + try { + const user = await ScalarStore.getTokenOwner(scalarToken, ignoreUpstreams); + Cache.for(CACHE_SCALAR_ACCOUNTS).put(scalarToken, user.userId, 30 * 60 * 1000); // 30 minutes + return user.userId; + } catch (err) { + LogService.error("ScalarService", err); + throw new ApiError(401, "Invalid token"); + } } @POST diff --git a/src/db/AppserviceStore.ts b/src/db/AppserviceStore.ts index fce1b91..ce4ea6c 100644 --- a/src/db/AppserviceStore.ts +++ b/src/db/AppserviceStore.ts @@ -2,7 +2,6 @@ import AppService from "./models/AppService"; import AppServiceUser from "./models/AppServiceUser"; import * as randomString from "random-string"; import { MatrixAppserviceClient } from "../matrix/MatrixAppserviceClient"; -import { resolveIfExists } from "./DimensionStore"; import config from "../config"; export class AppserviceStore { @@ -21,11 +20,15 @@ export class AppserviceStore { } public static async getUser(appserviceId: string, userId: string): Promise { - return AppServiceUser.findOne({where: {appserviceId: appserviceId, id: userId}}).then(resolveIfExists); + const user = await AppServiceUser.findOne({where: {appserviceId: appserviceId, id: userId}}); + if (!user) throw new Error("User not found"); + return user; } public static async getByHomeserverToken(hsToken: string): Promise { - return AppService.findOne({where: {hsToken: hsToken}}).then(resolveIfExists); + const appservice = AppService.findOne({where: {hsToken: hsToken}}); + if (!appservice) throw new Error("Appservice not found"); + return appservice; } public static async getAllByUserPrefix(userPrefix: string): Promise { @@ -44,16 +47,18 @@ export class AppserviceStore { 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); - const localpart = userId.substring(1).split(":")[0]; - return client.registerUser(localpart); - }).then(response => { - return AppServiceUser.create({ - id: userId, - appserviceId: appserviceId, - accessToken: response.access_token, - }); + + const appservice = await AppService.findOne({where: {id: appserviceId}}); + if (!appservice) throw new Error("Appservice not found"); + + const client = new MatrixAppserviceClient(config.homeserver.name, appservice); + const localpart = userId.substring(1).split(":")[0]; + const response = await client.registerUser(localpart); + + return AppServiceUser.create({ + id: userId, + appserviceId: appserviceId, + accessToken: response.access_token, }); } diff --git a/src/db/DimensionStore.ts b/src/db/DimensionStore.ts index ecf05a3..6a37bc3 100644 --- a/src/db/DimensionStore.ts +++ b/src/db/DimensionStore.ts @@ -56,9 +56,4 @@ class _DimensionStore { } } -export const DimensionStore = new _DimensionStore(); - -export function resolveIfExists(record: T): Promise { - if (!record) return Promise.reject("Record not found"); - return Promise.resolve(record); -} \ No newline at end of file +export const DimensionStore = new _DimensionStore(); \ No newline at end of file diff --git a/src/db/NebStore.ts b/src/db/NebStore.ts index 0df0406..1ff89f4 100644 --- a/src/db/NebStore.ts +++ b/src/db/NebStore.ts @@ -1,4 +1,3 @@ -import { resolveIfExists } from "./DimensionStore"; import { NebConfig } from "../models/neb"; import NebConfiguration from "./models/NebConfiguration"; import NebIntegration from "./models/NebIntegration"; @@ -77,66 +76,67 @@ export class NebStore { }; public static async getAllConfigs(): Promise { - return NebConfiguration.findAll().then(configs => { - return Promise.all((configs || []).map(c => NebStore.getConfig(c.id))); - }); + const configs = await NebConfiguration.findAll(); + return Promise.all((configs || []).map(c => NebStore.getConfig(c.id))); } public static async getConfig(id: number): Promise { - let nebConfig: NebConfiguration; - return NebConfiguration.findByPrimary(id).then(resolveIfExists).then(conf => { - nebConfig = conf; - return NebIntegration.findAll({where: {nebId: id}}); - }).then(integrations => { - return NebStore.getCompleteIntegrations(nebConfig, integrations); - }).then(integrations => { - return new NebConfig(nebConfig, integrations); - }); + const config = await NebConfiguration.findByPrimary(id); + if (!config) throw new Error("Configuration not found"); + + const integrations = await NebIntegration.findAll({where: {nebId: id}}); + const fullIntegrations = await NebStore.getCompleteIntegrations(config, integrations); + + return new NebConfig(config, fullIntegrations); } public static async createForUpstream(upstreamId: number): Promise { - return Upstream.findByPrimary(upstreamId).then(resolveIfExists).then(upstream => { - return NebConfiguration.create({ - upstreamId: upstream.id, - }); - }).then(config => { - return NebStore.getConfig(config.id); + const upstream = await Upstream.findByPrimary(upstreamId); + if (!upstream) throw new Error("Upstream not found"); + + const config = await NebConfiguration.create({ + upstreamId: upstream.id, }); + + return NebStore.getConfig(config.id); } public static async createForAppservice(appserviceId: string, adminUrl: string): Promise { - return AppService.findByPrimary(appserviceId).then(resolveIfExists).then(appservice => { - return NebConfiguration.create({ - appserviceId: appservice.id, - adminUrl: adminUrl, - }); - }).then(config => { - return NebStore.getConfig(config.id); + const appservice = await AppService.findByPrimary(appserviceId); + if (!appservice) throw new Error("Appservice not found"); + + const config = await NebConfiguration.create({ + appserviceId: appservice.id, + adminUrl: adminUrl, }); + + return NebStore.getConfig(config.id); } public static async getOrCreateIntegration(configurationId: number, integrationType: string): Promise { - if (!NebStore.INTEGRATIONS[integrationType]) return Promise.reject(new Error("Integration not supported")); + if (!NebStore.INTEGRATIONS[integrationType]) throw new Error("Integration not supported"); - return NebConfiguration.findByPrimary(configurationId).then(resolveIfExists).then(config => { - return NebIntegration.findOne({where: {nebId: config.id, type: integrationType}}); - }).then(integration => { - if (!integration) { - LogService.info("NebStore", "Creating integration " + integrationType + " for NEB " + configurationId); - return NebIntegration.create({ - type: integrationType, - name: NebStore.INTEGRATIONS[integrationType].name, - avatarUrl: NebStore.INTEGRATIONS[integrationType].avatarUrl, - description: NebStore.INTEGRATIONS[integrationType].description, - isEnabled: false, - isPublic: true, - nebId: configurationId, - }); - } else return Promise.resolve(integration); - }); + const config = await NebConfiguration.findByPrimary(configurationId); + if (!config) throw new Error("Configuration not found"); + + let integration = await NebIntegration.findOne({where: {nebId: config.id, type: integrationType}}); + if (!integration) { + LogService.info("NebStore", "Creating integration " + integrationType + " for NEB " + configurationId); + integration = await NebIntegration.create({ + type: integrationType, + name: NebStore.INTEGRATIONS[integrationType].name, + avatarUrl: NebStore.INTEGRATIONS[integrationType].avatarUrl, + description: NebStore.INTEGRATIONS[integrationType].description, + isEnabled: false, + isPublic: true, + nebId: configurationId, + }); + } + + return integration; } - public static getCompleteIntegrations(nebConfig: NebConfiguration, knownIntegrations: NebIntegration[]): Promise { + public static async getCompleteIntegrations(nebConfig: NebConfiguration, knownIntegrations: NebIntegration[]): Promise { const supported = NebStore.getSupportedIntegrations(nebConfig); const notSupported: SupportedIntegration[] = []; for (const supportedIntegration of supported) { diff --git a/src/db/ScalarStore.ts b/src/db/ScalarStore.ts index 99bb37a..812e2bd 100644 --- a/src/db/ScalarStore.ts +++ b/src/db/ScalarStore.ts @@ -6,48 +6,41 @@ import User from "./models/User"; export class ScalarStore { public static async doesUserHaveTokensForAllUpstreams(userId: string): Promise { - let upstreamTokenIds: number[] = []; - let hasDimensionToken = false; - return UserScalarToken.findAll({where: {userId: userId}}).then(results => { - upstreamTokenIds = results.filter(t => !t.isDimensionToken).map(t => t.upstreamId); - hasDimensionToken = results.filter(t => t.isDimensionToken).length >= 1; - return Upstream.findAll(); - }).then(upstreams => { - if (!hasDimensionToken) { - LogService.warn("DimensionStore", "User " + userId + " is missing a Dimension scalar token"); + const scalarTokens = await UserScalarToken.findAll({where: {userId: userId}}); + const upstreamTokenIds = scalarTokens.filter(t => !t.isDimensionToken).map(t => t.upstreamId); + const hasDimensionToken = scalarTokens.filter(t => t.isDimensionToken).length >= 1; + + if (!hasDimensionToken) { + LogService.warn("ScalarStore", "User " + userId + " is missing a Dimension scalar token"); + return false; + } + + const upstreams = await Upstream.findAll(); + for (const upstream of upstreams) { + if (upstreamTokenIds.indexOf(upstream.id) === -1) { + LogService.warn("ScalarStore", "user " + userId + " is missing a scalar token for upstream " + upstream.id + " (" + upstream.name + ")"); return false; } + } - for (const upstream of upstreams) { - if (upstreamTokenIds.indexOf(upstream.id) === -1) { - LogService.warn("DimensionStore", "User " + userId + " is missing a scalar token for upstream " + upstream.id + " (" + upstream.name + ")"); - return false; - } - } - - return true; - }); + return true; } public static async getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise { - let user: User = null; - return UserScalarToken.findAll({ + const tokens = await UserScalarToken.findAll({ where: {isDimensionToken: true, scalarToken: scalarToken}, include: [User] - }).then(tokens => { - if (!tokens || tokens.length === 0) { - return Promise.reject("Invalid token"); - } - - user = tokens[0].user; - if (ignoreUpstreams) return true; // they have all the upstreams as far as we're concerned - return ScalarStore.doesUserHaveTokensForAllUpstreams(user.userId); - }).then(hasUpstreams => { - if (!hasUpstreams) { - return Promise.reject("Invalid token"); // missing one or more upstreams == no validation - } - return Promise.resolve(user); }); + if (!tokens || tokens.length === 0) throw new Error("Invalid token"); + + const user = tokens[0].user; + if (ignoreUpstreams) return user; // skip upstreams check + + const hasAllTokens = await ScalarStore.doesUserHaveTokensForAllUpstreams(user.userId); + if (!hasAllTokens) { + throw new Error("Invalid token"); // They are missing an upstream, so we'll lie and say they are not authorized + } + return user; } private constructor() { diff --git a/src/db/WidgetStore.ts b/src/db/WidgetStore.ts index 4a00ea8..8626dd9 100644 --- a/src/db/WidgetStore.ts +++ b/src/db/WidgetStore.ts @@ -1,28 +1,30 @@ import WidgetRecord from "./models/WidgetRecord"; import { Widget } from "../integrations/Widget"; -import { resolveIfExists } from "./DimensionStore"; export class WidgetStore { 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))); + return (await WidgetRecord.findAll(conditions)).map(w => new Widget(w)); } 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(); - }); + const widget = await WidgetRecord.findOne({where: {type: type}}); + if (!widget) throw new Error("Widget not found"); + + widget.isEnabled = isEnabled; + return widget.save(); } 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; - return widget.save(); - }); + + const widget = await WidgetRecord.findOne({where: {type: type}}); + if (!widget) throw new Error("Widget not found"); + + widget.optionsJson = optionsJson; + return await widget.save(); } private constructor() { diff --git a/src/matrix/MatrixAppserviceClient.ts b/src/matrix/MatrixAppserviceClient.ts index 4afefbc..c6425af 100644 --- a/src/matrix/MatrixAppserviceClient.ts +++ b/src/matrix/MatrixAppserviceClient.ts @@ -13,7 +13,7 @@ export class MatrixAppserviceClient { constructor(private homeserverName: string, private appservice: AppService) { } - public registerUser(localpart: string): Promise { + public async registerUser(localpart: string): Promise { return doFederatedApiCall( "POST", this.homeserverName, diff --git a/src/matrix/MatrixLiteClient.ts b/src/matrix/MatrixLiteClient.ts index e13958a..4319bdf 100644 --- a/src/matrix/MatrixLiteClient.ts +++ b/src/matrix/MatrixLiteClient.ts @@ -10,29 +10,26 @@ export class MatrixLiteClient { constructor(private homeserverName: string, private accessToken: string) { } - public getFederationUrl(): Promise { + public async getFederationUrl(): Promise { return getFedUrl(this.homeserverName); } - public getUrlPreview(url: string): Promise { + public async getUrlPreview(url: string): Promise { return doFederatedApiCall( "GET", this.homeserverName, "/_matrix/media/r0/preview_url", {access_token: this.accessToken, url: url} - ).then(response => { - return response; - }); + ); } - public whoAmI(): Promise { - return doFederatedApiCall( + public async whoAmI(): Promise { + const response = await doFederatedApiCall( "GET", this.homeserverName, "/_matrix/client/r0/account/whoami", {access_token: this.accessToken} - ).then(response => { - return response["user_id"]; - }); + ); + return response['user_id']; } } diff --git a/src/matrix/MatrixOpenIdClient.ts b/src/matrix/MatrixOpenIdClient.ts index 7756083..f71e84e 100644 --- a/src/matrix/MatrixOpenIdClient.ts +++ b/src/matrix/MatrixOpenIdClient.ts @@ -6,14 +6,13 @@ export class MatrixOpenIdClient { constructor(private openId: OpenId) { } - public getUserId(): Promise { - return doFederatedApiCall( + public async getUserId(): Promise { + const response = await doFederatedApiCall( "GET", this.openId.matrix_server_name, "/_matrix/federation/v1/openid/userinfo", {access_token: this.openId.access_token} - ).then(response => { - return response['sub']; - }); + ); + return response['sub']; } } diff --git a/src/matrix/helpers.ts b/src/matrix/helpers.ts index 2b3d2d2..81911fe 100644 --- a/src/matrix/helpers.ts +++ b/src/matrix/helpers.ts @@ -3,57 +3,58 @@ import { LogService } from "matrix-js-snippets"; import { Cache, CACHE_FEDERATION } from "../MemoryCache"; import * as request from "request"; -export function getFederationUrl(serverName: string): Promise { +export async function getFederationUrl(serverName: string): Promise { const cachedUrl = Cache.for(CACHE_FEDERATION).get(serverName); if (cachedUrl) { LogService.verbose("matrix", "Cached federation URL for " + serverName + " is " + cachedUrl); - return Promise.resolve(cachedUrl); + return cachedUrl; } let serverUrl = null; let expirationMs = 4 * 60 * 60 * 1000; // default is 4 hours - const dnsPromise = dns.resolveSrv("_matrix._tcp." + serverName); - return Promise.resolve(dnsPromise).then(records => { + + try { + const records = await dns.resolveSrv("_matrix._tcp." + serverName); if (records && records.length > 0) { serverUrl = "https://" + records[0].name + ":" + records[0].port; expirationMs = records[0].ttl * 1000; } - }, _err => { + } catch (err) { // Not having the SRV record isn't bad, it just means that the server operator decided to not use SRV records. // When there's no SRV record we default to port 8448 (as per the federation rules) in the lower .then() // People tend to think that the lack of an SRV record is bad, but in reality it's only a problem if one was set and // it's not being found. Most people don't set up the SRV record, but some do. + LogService.verbose("matrix", err); LogService.warn("matrix", "Could not find _matrix._tcp." + serverName + " DNS record. This is normal for most servers."); - }).then(() => { - if (!serverUrl) serverUrl = "https://" + serverName + ":8448"; - LogService.verbose("matrix", "Federation URL for " + serverName + " is " + serverUrl + " - caching for " + expirationMs + " ms"); - Cache.for(CACHE_FEDERATION).put(serverName, serverUrl, expirationMs); - return serverUrl; - }); + } + + if (!serverUrl) serverUrl = "https://" + serverName + ":8448"; + LogService.verbose("matrix", "Federation URL for " + serverName + " is " + serverUrl + " - caching for " + expirationMs + " ms"); + Cache.for(CACHE_FEDERATION).put(serverName, serverUrl, expirationMs); + return serverUrl; } -export function doFederatedApiCall(method: string, serverName: string, endpoint: string, query?: object, body?: object): Promise { - return getFederationUrl(serverName).then(federationUrl => { - return new Promise((resolve, reject) => { - request({ - method: method, - url: federationUrl + endpoint, - qs: query, - json: body, - rejectUnauthorized: false, // allow self signed certs (for federation) - }, (err, res, _body) => { - if (err) { - LogService.error("matrix", "Error calling " + endpoint); - LogService.error("matrix", err); - reject(err); - } else if (res.statusCode !== 200) { - LogService.error("matrix", "Got status code " + res.statusCode + " while calling " + endpoint); - reject(new Error("Error in request: invalid status code")); - } else { - if (typeof(res.body) === "string") res.body = JSON.parse(res.body); - resolve(res.body); - } - }); +export async function doFederatedApiCall(method: string, serverName: string, endpoint: string, query?: object, body?: object): Promise { + const federationUrl = await getFederationUrl(serverName); + return new Promise((resolve, reject) => { + request({ + method: method, + url: federationUrl + endpoint, + qs: query, + json: body, + rejectUnauthorized: false, // allow self signed certs (for federation) + }, (err, res, _body) => { + if (err) { + LogService.error("matrix", "Error calling " + endpoint); + LogService.error("matrix", err); + reject(err); + } else if (res.statusCode !== 200) { + LogService.error("matrix", "Got status code " + res.statusCode + " while calling " + endpoint); + reject(new Error("Error in request: invalid status code")); + } else { + if (typeof(res.body) === "string") res.body = JSON.parse(res.body); + resolve(res.body); + } }); }); } \ No newline at end of file