Use async/await throughout the backend

This commit is contained in:
Travis Ralston 2018-03-23 21:26:14 -06:00
parent 8d6c2dfa00
commit 7c757a79e6
10 changed files with 155 additions and 160 deletions

View File

@ -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

View File

@ -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<AppServiceUser> {
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<AppService> {
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<AppService[]> {
@ -44,16 +47,18 @@ export class AppserviceStore {
public static async registerUser(appserviceId: string, userId: string): Promise<AppServiceUser> {
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,
});
}

View File

@ -57,8 +57,3 @@ class _DimensionStore {
}
export const DimensionStore = new _DimensionStore();
export function resolveIfExists<T>(record: T): Promise<T> {
if (!record) return Promise.reject("Record not found");
return Promise.resolve(record);
}

View File

@ -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<NebConfig[]> {
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<NebConfig> {
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<NebConfig> {
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<NebConfig> {
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<NebIntegration> {
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<NebIntegration[]> {
public static async getCompleteIntegrations(nebConfig: NebConfiguration, knownIntegrations: NebIntegration[]): Promise<NebIntegration[]> {
const supported = NebStore.getSupportedIntegrations(nebConfig);
const notSupported: SupportedIntegration[] = [];
for (const supportedIntegration of supported) {

View File

@ -6,48 +6,41 @@ import User from "./models/User";
export class ScalarStore {
public static async doesUserHaveTokensForAllUpstreams(userId: string): Promise<boolean> {
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<User> {
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() {

View File

@ -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<Widget[]> {
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<any> {
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<any> {
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() {

View File

@ -13,7 +13,7 @@ export class MatrixAppserviceClient {
constructor(private homeserverName: string, private appservice: AppService) {
}
public registerUser(localpart: string): Promise<MatrixUserResponse> {
public async registerUser(localpart: string): Promise<MatrixUserResponse> {
return doFederatedApiCall(
"POST",
this.homeserverName,

View File

@ -10,29 +10,26 @@ export class MatrixLiteClient {
constructor(private homeserverName: string, private accessToken: string) {
}
public getFederationUrl(): Promise<string> {
public async getFederationUrl(): Promise<string> {
return getFedUrl(this.homeserverName);
}
public getUrlPreview(url: string): Promise<MatrixUrlPreview> {
public async getUrlPreview(url: string): Promise<MatrixUrlPreview> {
return doFederatedApiCall(
"GET",
this.homeserverName,
"/_matrix/media/r0/preview_url",
{access_token: this.accessToken, url: url}
).then(response => {
return response;
});
);
}
public whoAmI(): Promise<string> {
return doFederatedApiCall(
public async whoAmI(): Promise<string> {
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'];
}
}

View File

@ -6,14 +6,13 @@ export class MatrixOpenIdClient {
constructor(private openId: OpenId) {
}
public getUserId(): Promise<string> {
return doFederatedApiCall(
public async getUserId(): Promise<string> {
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'];
}
}

View File

@ -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<string> {
export async function getFederationUrl(serverName: string): Promise<string> {
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<any> {
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<any> {
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);
}
});
});
}