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"; import User from "../../db/models/User"; import UserScalarToken from "../../db/models/UserScalarToken"; import { LogService } from "matrix-js-snippets"; import { ApiError } from "../ApiError"; import * as randomString from "random-string"; import { OpenId } from "../../models/OpenId"; import { ScalarAccountResponse, ScalarRegisterResponse } from "../../models/ScalarResponses"; import { DimensionStore } from "../../db/DimensionStore"; import { MemoryCache } from "../../MemoryCache"; interface RegisterRequest { access_token: string; token_type: string; matrix_server_name: string; expires_in: number; } @Path("/api/v1/scalar") export class ScalarService { private static accountCache = new MemoryCache(); public static clearAccountCache(): void { ScalarService.accountCache.clear(); } public static getTokenOwner(scalarToken: string): Promise { const cachedUserId = ScalarService.accountCache.get(scalarToken); if (cachedUserId) return Promise.resolve(cachedUserId); return DimensionStore.getTokenOwner(scalarToken).then(user => { if (!user) return Promise.reject("Invalid token"); ScalarService.accountCache.put(scalarToken, user.userId, 30 * 60 * 1000); // 30 minutes return Promise.resolve(user.userId); }); } 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"}); } @POST @Path("register") public register(request: RegisterRequest): Promise { let userId = null; 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, }); }); } }); })); }).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"}); }); } @GET @Path("account") public getAccount(@QueryParam("scalar_token") scalarToken: string): Promise { return ScalarService.getTokenOwner(scalarToken).then(userId => { return {user_id: userId}; }, ScalarService.invalidTokenErrorHandler); } }