2017-12-24 09:02:57 +00:00
|
|
|
import UserScalarToken from "./models/UserScalarToken";
|
|
|
|
import { LogService } from "matrix-js-snippets";
|
|
|
|
import Upstream from "./models/Upstream";
|
|
|
|
import User from "./models/User";
|
2019-04-13 22:58:20 +00:00
|
|
|
import { MatrixStickerBot } from "../matrix/MatrixStickerBot";
|
|
|
|
import { ScalarClient } from "../scalar/ScalarClient";
|
2019-07-11 01:30:06 +00:00
|
|
|
import { Cache, CACHE_SCALAR_ONLINE_STATE } from "../MemoryCache";
|
2019-07-23 00:09:25 +00:00
|
|
|
import { ILanguagePolicy } from "../api/controllers/TermsController";
|
2017-12-24 09:02:57 +00:00
|
|
|
|
|
|
|
export class ScalarStore {
|
|
|
|
|
2019-07-11 03:16:04 +00:00
|
|
|
public static async doesUserHaveTokensForAllUpstreams(userId: string, scalarKind: string): Promise<boolean> {
|
2018-03-24 03:26:14 +00:00
|
|
|
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;
|
|
|
|
}
|
2017-12-24 09:02:57 +00:00
|
|
|
|
2018-03-24 03:26:14 +00:00
|
|
|
const upstreams = await Upstream.findAll();
|
|
|
|
for (const upstream of upstreams) {
|
2019-07-11 03:16:04 +00:00
|
|
|
if (!await ScalarStore.isUpstreamOnline(upstream, scalarKind)) {
|
2019-04-13 22:58:20 +00:00
|
|
|
LogService.warn("ScalarStore", `Upstream ${upstream.apiUrl} is offline - assuming token is valid`);
|
|
|
|
continue;
|
|
|
|
}
|
2018-03-24 03:26:14 +00:00
|
|
|
if (upstreamTokenIds.indexOf(upstream.id) === -1) {
|
|
|
|
LogService.warn("ScalarStore", "user " + userId + " is missing a scalar token for upstream " + upstream.id + " (" + upstream.name + ")");
|
|
|
|
return false;
|
2017-12-24 09:02:57 +00:00
|
|
|
}
|
2018-03-24 03:26:14 +00:00
|
|
|
}
|
2017-12-24 09:02:57 +00:00
|
|
|
|
2018-03-24 03:26:14 +00:00
|
|
|
return true;
|
2017-12-24 09:02:57 +00:00
|
|
|
}
|
|
|
|
|
2019-07-11 01:30:06 +00:00
|
|
|
public static async getTokenOwner(scalarToken: string): Promise<User> {
|
2018-03-24 03:26:14 +00:00
|
|
|
const tokens = await UserScalarToken.findAll({
|
2017-12-24 09:02:57 +00:00
|
|
|
where: {isDimensionToken: true, scalarToken: scalarToken},
|
|
|
|
include: [User]
|
|
|
|
});
|
2018-03-24 03:26:14 +00:00
|
|
|
if (!tokens || tokens.length === 0) throw new Error("Invalid token");
|
|
|
|
|
2019-07-11 01:30:06 +00:00
|
|
|
return tokens[0].user;
|
2017-12-24 09:02:57 +00:00
|
|
|
}
|
|
|
|
|
2019-07-11 03:16:04 +00:00
|
|
|
public static async isUpstreamOnline(upstream: Upstream, scalarKind: string): Promise<boolean> {
|
2019-04-13 22:58:20 +00:00
|
|
|
const cache = Cache.for(CACHE_SCALAR_ONLINE_STATE);
|
|
|
|
const cacheKey = `Upstream ${upstream.id}`;
|
|
|
|
const result = cache.get(cacheKey);
|
|
|
|
if (typeof (result) === 'boolean') {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
2019-07-11 03:16:04 +00:00
|
|
|
const state = ScalarStore.checkIfUpstreamOnline(upstream, scalarKind);
|
2019-04-13 22:58:20 +00:00
|
|
|
cache.put(cacheKey, state, 60 * 60 * 1000); // 1 hour
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2019-07-23 00:09:25 +00:00
|
|
|
private static async checkIfUpstreamOnline(upstream: Upstream, scalarKind: string, signTerms = true): Promise<boolean> {
|
2019-04-13 22:58:20 +00:00
|
|
|
try {
|
|
|
|
// The sticker bot can be used for this for now
|
|
|
|
|
|
|
|
const testUserId = await MatrixStickerBot.getUserId();
|
2019-07-11 03:16:04 +00:00
|
|
|
const scalarClient = new ScalarClient(upstream, scalarKind);
|
2019-04-13 22:58:20 +00:00
|
|
|
|
|
|
|
// First see if we have a token for the upstream so we can try it
|
|
|
|
const existingTokens = await UserScalarToken.findAll({
|
|
|
|
where: {isDimensionToken: false, userId: testUserId, upstreamId: upstream.id},
|
2019-08-03 18:40:52 +00:00
|
|
|
include: [Upstream],
|
2019-04-13 22:58:20 +00:00
|
|
|
});
|
|
|
|
if (existingTokens && existingTokens.length) {
|
|
|
|
// Test that the token works
|
|
|
|
try {
|
|
|
|
const result = await scalarClient.getAccount(existingTokens[0].scalarToken);
|
|
|
|
if (result.user_id !== testUserId) {
|
|
|
|
// noinspection ExceptionCaughtLocallyJS
|
|
|
|
throw new Error(`Unexpected error: Upstream ${upstream.id} did not return account info for the right token`);
|
|
|
|
}
|
|
|
|
return true; // it's online
|
|
|
|
} catch (e) {
|
|
|
|
LogService.error("ScalarStore", e);
|
2019-07-23 00:09:25 +00:00
|
|
|
if (e && !isNaN(Number(e.statusCode))) {
|
|
|
|
if (e.statusCode === 403 && e.body) {
|
|
|
|
if (e.body.errcode === 'M_TERMS_NOT_SIGNED' && signTerms) {
|
|
|
|
await ScalarStore.signAllTerms(existingTokens[0], scalarKind);
|
|
|
|
return ScalarStore.checkIfUpstreamOnline(upstream, scalarKind, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (e.statusCode === 401 || e.statusCode === 403) {
|
2019-04-13 22:58:20 +00:00
|
|
|
LogService.info("ScalarStore", "Test user token expired");
|
|
|
|
} else {
|
|
|
|
// Assume offline
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we're here, we need to register a new token
|
|
|
|
|
|
|
|
if (existingTokens && existingTokens.length) {
|
|
|
|
for (const token of existingTokens) {
|
|
|
|
await token.destroy();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-18 05:34:14 +00:00
|
|
|
const user = await User.findByPk(testUserId);
|
|
|
|
if (!user) {
|
|
|
|
await User.create({
|
|
|
|
userId: testUserId,
|
|
|
|
isSelfBot: true,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2019-04-13 22:58:20 +00:00
|
|
|
const openId = await MatrixStickerBot.getOpenId();
|
|
|
|
const token = await scalarClient.register(openId);
|
2019-07-23 00:09:25 +00:00
|
|
|
const scalarToken = await UserScalarToken.create({
|
2019-04-13 22:58:20 +00:00
|
|
|
userId: testUserId,
|
|
|
|
scalarToken: token.scalar_token,
|
|
|
|
isDimensionToken: false,
|
|
|
|
upstreamId: upstream.id,
|
|
|
|
});
|
2019-08-03 18:40:52 +00:00
|
|
|
scalarToken.upstream = upstream;
|
2019-04-13 22:58:20 +00:00
|
|
|
|
2019-07-23 00:09:25 +00:00
|
|
|
// Accept all terms of service for the user
|
|
|
|
await ScalarStore.signAllTerms(scalarToken, scalarKind);
|
|
|
|
|
2019-04-13 22:58:20 +00:00
|
|
|
return true;
|
|
|
|
} catch (e) {
|
|
|
|
LogService.error("ScalarStore", e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-23 00:09:25 +00:00
|
|
|
private static async signAllTerms(token: UserScalarToken, scalarKind: string) {
|
|
|
|
try {
|
|
|
|
const client = new ScalarClient(token.upstream, scalarKind);
|
|
|
|
const terms = await client.getAvailableTerms();
|
|
|
|
const urlsToSign = Object.values(terms.policies).map(p => {
|
|
|
|
const englishCode = Object.keys(p).find(k => k.toLowerCase() === 'en' || k.toLowerCase().startsWith('en_'));
|
|
|
|
if (!englishCode) return null;
|
|
|
|
return (<ILanguagePolicy>p[englishCode]).url;
|
|
|
|
}).filter(v => !!v);
|
|
|
|
await client.signTermsUrls(token.scalarToken, urlsToSign);
|
|
|
|
} catch (e) {
|
|
|
|
LogService.error("ScalarStore", e);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-24 09:09:40 +00:00
|
|
|
private constructor() {
|
|
|
|
}
|
2017-12-24 09:02:57 +00:00
|
|
|
}
|