Convert the frontend to the async/await pattern

We have to drop bluebird to be able to do this.
This commit is contained in:
Travis Ralston 2018-03-23 21:01:56 -06:00
parent 51740308a2
commit 8d6c2dfa00
26 changed files with 243 additions and 298 deletions

View File

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

View File

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

View File

@ -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<string> {
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<string> {
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<DimensionVersionResponse> {
return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => {
return {version: CURRENT_VERSION};
});
public async getVersion(@QueryParam("scalar_token") scalarToken: string): Promise<DimensionVersionResponse> {
await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken);
return {version: CURRENT_VERSION};
}
@GET
@Path("config")
public getConfig(@QueryParam("scalar_token") scalarToken: string): Promise<DimensionConfigResponse> {
public async getConfig(@QueryParam("scalar_token") scalarToken: string): Promise<DimensionConfigResponse> {
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);
}
}

View File

@ -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<AppserviceResponse[]> {
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<AppserviceResponse[]> {
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<AppserviceResponse> {
public async createAppservice(@QueryParam("scalar_token") scalarToken: string, request: AppserviceCreateRequest): Promise<AppserviceResponse> {
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<UserResponse[]> {
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<UserResponse[]> {
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<UserResponse> {
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<UserResponse> {
await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken);
const user = await AppserviceStore.registerUser(asId, request.userId);
return this.mapUser(user);
}
private mapAppservice(as: AppService): AppserviceResponse {

View File

@ -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<any> {
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<any> {
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<any> {
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<any> {
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<IntegrationsResponse> {
return DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken).then(_userId => {
return DimensionIntegrationsService.getIntegrations(null);
});
public async getAllIntegrations(@QueryParam("scalar_token") scalarToken: string): Promise<IntegrationsResponse> {
await DimensionAdminService.validateAndGetAdminTokenOwner(scalarToken);
return DimensionIntegrationsService.getIntegrations(null);
}
}

View File

@ -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<IntegrationsResponse> {
const cachedResponse = Cache.for(CACHE_INTEGRATIONS).get("integrations_" + isEnabledCheck);
if (cachedResponse) {
return cachedResponse;
public static async getIntegrations(isEnabledCheck?: boolean): Promise<IntegrationsResponse> {
const cachedWidgets = Cache.for(CACHE_INTEGRATIONS).get("integrations_" + isEnabledCheck);
if (cachedWidgets) {
return {widgets: cachedWidgets};
}
const response = <IntegrationsResponse>{
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<IntegrationsResponse> {
return ScalarService.getTokenOwner(scalarToken).then(_userId => {
return DimensionIntegrationsService.getIntegrations(true);
}, ScalarService.invalidTokenErrorHandler);
public async getEnabledIntegrations(@QueryParam("scalar_token") scalarToken: string): Promise<IntegrationsResponse> {
await ScalarService.getTokenOwner(scalarToken);
return DimensionIntegrationsService.getIntegrations(true);
}
@GET
@Path("room/:roomId")
public getIntegrationsInRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string): Promise<IntegrationsResponse> {
public async getIntegrationsInRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string): Promise<IntegrationsResponse> {
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<Integration> {
public async getIntegration(@PathParam("category") category: string, @PathParam("type") type: string): Promise<Integration> {
// 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 <Integration[]>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");
}
}

View File

@ -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<NebConfig[]> {
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<NebConfig[]> {
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<NebConfig> {
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<NebConfig> {
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<any> {
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<any> {
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<NebConfig> {
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<NebConfig> {
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<NebConfig> {
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<NebConfig> {
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");
}
}
}

View File

@ -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<UpstreamRepsonse[]> {
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<UpstreamRepsonse[]> {
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<UpstreamRepsonse> {
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<UpstreamRepsonse> {
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 {

View File

@ -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<any> {
return AppserviceStore.getByHomeserverToken(homeserverToken).then(appservice => {
public async onTransaction(@QueryParam("access_token") homeserverToken: string, @PathParam("txnId") txnId: string, _txn: AppServiceTransaction): Promise<any> {
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<any> {
return AppserviceStore.getByHomeserverToken(homeserverToken).then(appservice => {
public async getRoom(@QueryParam("access_token") homeserverToken: string, @PathParam("alias") roomAlias: string): Promise<any> {
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<any> {
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<any> {
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,
};
});
}
}
}

View File

@ -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<string> {
public static async getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise<string> {
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<ScalarRegisterResponse> {
let userId = null;
public async register(request: RegisterRequest): Promise<ScalarRegisterResponse> {
const mxClient = new MatrixOpenIdClient(<OpenId>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(<OpenId>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(<OpenId>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<ScalarAccountResponse> {
return ScalarService.getTokenOwner(scalarToken).then(userId => {
return {user_id: userId};
}, ScalarService.invalidTokenErrorHandler);
public async getAccount(@QueryParam("scalar_token") scalarToken: string): Promise<ScalarAccountResponse> {
const userId = await ScalarService.getTokenOwner(scalarToken);
return {user_id: userId};
}
}

View File

@ -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<UrlPreviewResponse> {
@GET
@Path("title_lookup")
public async titleLookup(@QueryParam("scalar_token") scalarToken: string, @QueryParam("curl") url: string): Promise<UrlPreviewResponse> {
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 <UrlPreviewResponse>{
@ -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<UrlPreviewResponse> {
return ScalarService.getTokenOwner(scalarToken).then(_userId => {
return ScalarWidgetService.getUrlTitle(url);
}, ScalarService.invalidTokenErrorHandler);
}
}
}

View File

@ -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<AppService> {
public static async create(userPrefix: string): Promise<AppService> {
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<AppServiceUser> {
public static async getUser(appserviceId: string, userId: string): Promise<AppServiceUser> {
return AppServiceUser.findOne({where: {appserviceId: appserviceId, id: userId}}).then(resolveIfExists);
}
public static getByHomeserverToken(hsToken: string): Promise<AppService> {
public static async getByHomeserverToken(hsToken: string): Promise<AppService> {
return AppService.findOne({where: {hsToken: hsToken}}).then(resolveIfExists);
}
public static getAllByUserPrefix(userPrefix: string): Promise<AppService[]> {
public static async getAllByUserPrefix(userPrefix: string): Promise<AppService[]> {
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<AppServiceUser[]> {
public static async getUsers(appserviceId: string): Promise<AppServiceUser[]> {
return AppServiceUser.findAll({where: {appserviceId: appserviceId}});
}
public static registerUser(appserviceId: string, userId: string): Promise<AppServiceUser> {
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);

View File

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

View File

@ -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<NebConfig[]> {
public static async getAllConfigs(): Promise<NebConfig[]> {
return NebConfiguration.findAll().then(configs => {
return Promise.all((configs || []).map(c => NebStore.getConfig(c.id)));
});
}
public static getConfig(id: number): Promise<NebConfig> {
public static async getConfig(id: number): Promise<NebConfig> {
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<NebConfig> {
public static async createForUpstream(upstreamId: number): Promise<NebConfig> {
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<NebConfig> {
public static async createForAppservice(appserviceId: string, adminUrl: string): Promise<NebConfig> {
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<NebIntegration> {
public static async getOrCreateIntegration(configurationId: number, integrationType: string): Promise<NebIntegration> {
if (!NebStore.INTEGRATIONS[integrationType]) return Promise.reject(new Error("Integration not supported"));
return NebConfiguration.findByPrimary(configurationId).then(resolveIfExists).then(config => {

View File

@ -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<boolean> {
public static async doesUserHaveTokensForAllUpstreams(userId: string): Promise<boolean> {
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<User> {
public static async getTokenOwner(scalarToken: string, ignoreUpstreams?: boolean): Promise<User> {
let user: User = null;
return UserScalarToken.findAll({
where: {isDimensionToken: true, scalarToken: scalarToken},

View File

@ -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<Widget[]> {
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)));
}
public static setEnabled(type: string, isEnabled: boolean): Promise<any> {
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();
});
}
public static setOptions(type: string, options: any): Promise<any> {
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;

View File

@ -1,6 +1,5 @@
import { QueryInterface } from "sequelize";
import { DataType } from "sequelize-typescript";
import * as Promise from "bluebird";
export default {
up: (queryInterface: QueryInterface) => {

View File

@ -1,6 +1,5 @@
import { QueryInterface } from "sequelize";
import { DataType } from "sequelize-typescript";
import * as Promise from "bluebird";
export default {
up: (queryInterface: QueryInterface) => {

View File

@ -1,6 +1,5 @@
import { QueryInterface } from "sequelize";
import { DataType } from "sequelize-typescript";
import * as Promise from "bluebird";
export default {
up: (queryInterface: QueryInterface) => {

View File

@ -1,6 +1,5 @@
import { QueryInterface } from "sequelize";
import { DataType } from "sequelize-typescript";
import * as Promise from "bluebird";
export default {
up: (queryInterface: QueryInterface) => {

View File

@ -1,6 +1,5 @@
import { QueryInterface } from "sequelize";
import { DataType } from "sequelize-typescript";
import * as Promise from "bluebird";
export default {
up: (queryInterface: QueryInterface) => {

View File

@ -1,4 +1,3 @@
import * as Promise from "bluebird";
import { doFederatedApiCall } from "./helpers";
import AppService from "../db/models/AppService";

View File

@ -1,4 +1,3 @@
import * as Promise from "bluebird";
import { doFederatedApiCall, getFederationUrl as getFedUrl } from "./helpers";
export interface MatrixUrlPreview {

View File

@ -1,4 +1,3 @@
import * as Promise from "bluebird";
import { doFederatedApiCall } from "./helpers";
import { OpenId } from "../models/OpenId";

View File

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

View File

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