diff --git a/src/api/controllers/AccountController.ts b/src/api/controllers/AccountController.ts index 76e86e3..e6aefa3 100644 --- a/src/api/controllers/AccountController.ts +++ b/src/api/controllers/AccountController.ts @@ -51,9 +51,10 @@ export default class AccountController { /** * Registers an account to use the Integration Manager * @param {OpenId} openId The OpenID request information. + * @param {string} scalarKind The kind of scalar client to use. * @returns {Promise} Resolves when registered. */ - public async registerAccount(openId: OpenId): Promise { + public async registerAccount(openId: OpenId, scalarKind: string): Promise { if (!openId || !openId.matrix_server_name || !openId.access_token) { throw new ApiError(400, "Missing OpenID information"); } @@ -76,7 +77,7 @@ export default class AccountController { const upstreams = await Upstream.findAll(); await Promise.all(upstreams.map(async upstream => { - if (!await ScalarStore.isUpstreamOnline(upstream)) { + if (!await ScalarStore.isUpstreamOnline(upstream, scalarKind)) { LogService.warn("AccountController", `Skipping registration for ${mxUserId} on upstream ${upstream.id} (${upstream.name}) because it is offline`); return null; } diff --git a/src/api/msc/MSCAccountService.ts b/src/api/msc/MSCAccountService.ts index 7a8abc3..3784dee 100644 --- a/src/api/msc/MSCAccountService.ts +++ b/src/api/msc/MSCAccountService.ts @@ -3,6 +3,7 @@ import { OpenId } from "../../models/OpenId"; import AccountController, { IAccountInfoResponse, IAccountRegisteredResponse } from "../controllers/AccountController"; import { AutoWired, Inject } from "typescript-ioc/es6"; import { IMSCUser, ROLE_MSC_USER } from "../security/MSCSecurity"; +import { ScalarClient } from "../../scalar/ScalarClient"; /** * API for account management @@ -20,7 +21,7 @@ export class MSCAccountService { @POST @Path("register") public async register(request: OpenId): Promise { - return this.accountController.registerAccount(request); + return this.accountController.registerAccount(request, ScalarClient.KIND_MATRIX_V1); } @GET diff --git a/src/api/scalar/ScalarService.ts b/src/api/scalar/ScalarService.ts index c42e3d1..8db25ca 100644 --- a/src/api/scalar/ScalarService.ts +++ b/src/api/scalar/ScalarService.ts @@ -7,6 +7,7 @@ import AccountController from "../controllers/AccountController"; import { ROLE_MSC_USER } from "../security/MSCSecurity"; import TermsController, { ITermsNotSignedResponse } from "../controllers/TermsController"; import { SignTermsRequest } from "../msc/MSCTermsService"; +import { ScalarClient } from "../../scalar/ScalarClient"; /** * API for the minimum Scalar API we need to implement to be compatible with clients. Used for registration @@ -32,7 +33,7 @@ export class ScalarService { throw new ApiError(401, "Invalid API version."); } - const response = await this.accountController.registerAccount(request); + const response = await this.accountController.registerAccount(request, ScalarClient.KIND_LEGACY); return {scalar_token: response.token}; } diff --git a/src/api/security/MSCSecurity.ts b/src/api/security/MSCSecurity.ts index ea65617..3799cd4 100644 --- a/src/api/security/MSCSecurity.ts +++ b/src/api/security/MSCSecurity.ts @@ -6,6 +6,7 @@ import AccountController from "../controllers/AccountController"; import TermsController from "../controllers/TermsController"; import config from "../../config"; import { ScalarStore } from "../../db/ScalarStore"; +import { ScalarClient } from "../../scalar/ScalarClient"; export interface IMSCUser { userId: string; @@ -74,7 +75,8 @@ export default class MSCSecurity implements ServiceAuthenticator { const needUpstreams = !this.matchesAnyRoute(req, ADMIN_ROUTES); if (needUpstreams) { - const hasUpstreams = await ScalarStore.doesUserHaveTokensForAllUpstreams(req.user.userId); + const scalarKind = req.path.startsWith("/_matrix/integrations/v1/") ? ScalarClient.KIND_MATRIX_V1 : ScalarClient.KIND_LEGACY; + const hasUpstreams = await ScalarStore.doesUserHaveTokensForAllUpstreams(req.user.userId, scalarKind); if (!hasUpstreams) { return res.status(401).json({errcode: "M_INVALID_TOKEN", error: "Invalid token"}); } diff --git a/src/db/ScalarStore.ts b/src/db/ScalarStore.ts index 1e7c642..a4d0108 100644 --- a/src/db/ScalarStore.ts +++ b/src/db/ScalarStore.ts @@ -8,7 +8,7 @@ import { Cache, CACHE_SCALAR_ONLINE_STATE } from "../MemoryCache"; export class ScalarStore { - public static async doesUserHaveTokensForAllUpstreams(userId: string): Promise { + public static async doesUserHaveTokensForAllUpstreams(userId: string, scalarKind: string): Promise { 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; @@ -20,7 +20,7 @@ export class ScalarStore { const upstreams = await Upstream.findAll(); for (const upstream of upstreams) { - if (!await ScalarStore.isUpstreamOnline(upstream)) { + if (!await ScalarStore.isUpstreamOnline(upstream, scalarKind)) { LogService.warn("ScalarStore", `Upstream ${upstream.apiUrl} is offline - assuming token is valid`); continue; } @@ -43,7 +43,7 @@ export class ScalarStore { return tokens[0].user; } - public static async isUpstreamOnline(upstream: Upstream): Promise { + public static async isUpstreamOnline(upstream: Upstream, scalarKind: string): Promise { const cache = Cache.for(CACHE_SCALAR_ONLINE_STATE); const cacheKey = `Upstream ${upstream.id}`; const result = cache.get(cacheKey); @@ -51,17 +51,17 @@ export class ScalarStore { return result; } - const state = ScalarStore.checkIfUpstreamOnline(upstream); + const state = ScalarStore.checkIfUpstreamOnline(upstream, scalarKind); cache.put(cacheKey, state, 60 * 60 * 1000); // 1 hour return state; } - private static async checkIfUpstreamOnline(upstream: Upstream): Promise { + private static async checkIfUpstreamOnline(upstream: Upstream, scalarKind: string): Promise { try { // The sticker bot can be used for this for now const testUserId = await MatrixStickerBot.getUserId(); - const scalarClient = new ScalarClient(upstream); + const scalarClient = new ScalarClient(upstream, scalarKind); // First see if we have a token for the upstream so we can try it const existingTokens = await UserScalarToken.findAll({ diff --git a/src/models/ScalarResponses.ts b/src/models/ScalarResponses.ts index a90dfa9..a2dab3f 100644 --- a/src/models/ScalarResponses.ts +++ b/src/models/ScalarResponses.ts @@ -5,4 +5,8 @@ export interface ScalarRegisterResponse { export interface ScalarAccountResponse { user_id: string; // credit: number; // present on scalar-web +} + +export interface ScalarLogoutResponse { + // Nothing of interest } \ No newline at end of file diff --git a/src/scalar/ScalarClient.ts b/src/scalar/ScalarClient.ts index 78656d2..bd57e72 100644 --- a/src/scalar/ScalarClient.ts +++ b/src/scalar/ScalarClient.ts @@ -1,21 +1,58 @@ import { OpenId } from "../models/OpenId"; -import { ScalarAccountResponse, ScalarRegisterResponse } from "../models/ScalarResponses"; +import { ScalarAccountResponse, ScalarLogoutResponse, ScalarRegisterResponse } from "../models/ScalarResponses"; import * as request from "request"; import { LogService } from "matrix-js-snippets"; import Upstream from "../db/models/Upstream"; import { SCALAR_API_VERSION } from "../utils/common-constants"; +import * as url from "url"; + +const REGISTER_ROUTE = "/register"; +const ACCOUNT_INFO_ROUTE = "/account"; +const LOGOUT_ROUTE = "/logout"; export class ScalarClient { - constructor(private upstream: Upstream) { + public static readonly KIND_LEGACY = "legacy"; + public static readonly KIND_MATRIX_V1 = "matrix_v1"; + + constructor(private upstream: Upstream, private kind = ScalarClient.KIND_LEGACY) { + } + + private makeRequestArguments(path: string, token: string): { scalarUrl: string, headers: any, queryString: any } { + if (this.kind === ScalarClient.KIND_LEGACY) { + const addlQuery = {}; + if (token) addlQuery['scalar_token'] = token; + return { + scalarUrl: this.upstream.scalarUrl + path, + headers: {}, + queryString: { + v: SCALAR_API_VERSION, + ...addlQuery, + }, + }; + } else { + const parsed = url.parse(this.upstream.scalarUrl); + parsed.path = '/_matrix/integrations/v1' + (path === ACCOUNT_INFO_ROUTE ? path : `${ACCOUNT_INFO_ROUTE}${path}`); + + const headers = {}; + if (token) headers['Authorization'] = `Bearer ${token}`; + + return { + scalarUrl: parsed.toString(), + headers: headers, + queryString: {}, + }; + } } public register(openId: OpenId): Promise { - LogService.info("ScalarClient", "Doing upstream scalar request: " + this.upstream.scalarUrl + "/register"); + const {scalarUrl, headers, queryString} = this.makeRequestArguments(REGISTER_ROUTE, null); + LogService.info("ScalarClient", "Doing upstream scalar request: " + scalarUrl); return new Promise((resolve, reject) => { request({ method: "POST", - url: this.upstream.scalarUrl + "/register", - qs: {v: SCALAR_API_VERSION}, + url: scalarUrl, + qs: queryString, + headers: headers, json: openId, }, (err, res, _body) => { if (err) { @@ -33,12 +70,39 @@ export class ScalarClient { } public getAccount(token: string): Promise { - LogService.info("ScalarClient", "Doing upstream scalar request: " + this.upstream.scalarUrl + "/account"); + const {scalarUrl, headers, queryString} = this.makeRequestArguments(ACCOUNT_INFO_ROUTE, token); + LogService.info("ScalarClient", "Doing upstream scalar request: " + scalarUrl); return new Promise((resolve, reject) => { request({ method: "GET", - url: this.upstream.scalarUrl + "/account", - qs: {v: SCALAR_API_VERSION, scalar_token: token}, + url: scalarUrl, + qs: queryString, + headers: headers, + json: true, + }, (err, res, _body) => { + if (err) { + LogService.error("ScalarClient", "Error getting information for token"); + LogService.error("ScalarClient", err); + reject(err); + } else if (res.statusCode !== 200) { + LogService.error("ScalarClient", "Got status code " + res.statusCode + " while getting information for token"); + reject(res.statusCode); + } else { + resolve(res.body); + } + }); + }); + } + + public logout(token: string): Promise { + const {scalarUrl, headers, queryString} = this.makeRequestArguments(LOGOUT_ROUTE, token); + LogService.info("ScalarClient", "Doing upstream scalar request: " + scalarUrl); + return new Promise((resolve, reject) => { + request({ + method: "POST", + url: scalarUrl, + qs: queryString, + headers: headers, json: true, }, (err, res, _body) => { if (err) {