@m-ld/gateway
Version:
m-ld gateway for services
238 lines • 23 kB
JavaScript
import { propertyValue, Reference, uuid } from '@m-ld/m-ld';
import { AccountOwnedId, BaseGateway, Env, GatewayPrincipal } from '../lib/index.js';
import { gatewayContext } from '../data/index.js';
import LOG from 'loglevel';
import { access, rm, writeFile } from 'fs/promises';
import { Subscription } from 'rxjs';
import { BadRequestError, ConflictError, UnauthorizedError } from '../http/errors.js';
import { Account } from './Account.js';
import { SubdomainClone } from './SubdomainClone.js';
import { randomInt } from 'crypto';
import jsonwebtoken from 'jsonwebtoken';
import Cryptr from 'cryptr';
import { Subdomain } from '../data/Subdomain.js';
export class Gateway extends BaseGateway {
// noinspection JSUnusedGlobalSymbols keyStore is in account context
constructor(env, config, cloneFactory, keyStore, subdomainCache) {
super(config['@domain']);
this.env = env;
this.cloneFactory = cloneFactory;
this.keyStore = keyStore;
this.subdomainCache = subdomainCache;
this.subs = new Subscription();
LOG.debug('Gateway domain is', this.domainName);
this.config = { ...config, '@context': gatewayContext };
this.me = new GatewayPrincipal(this.absoluteId('/'), this.config);
}
async initialise() {
// Load the gateway domain
const dataDir = await this.env.readyPath('data', 'gw');
[this.domain] = await this.cloneFactory.clone(this.config, dataDir);
await this.domain.status.becomes({ outdated: false });
// Create the gateway account with our key, if it doesn't exist
await this.domain.write(new Account(this, {
name: this.rootAccountName, keyids: [this.me.authKey.keyid]
}).toJSON());
this.subs.add(this.domain.follow(update => this.onUpdateSubdomains(update).then()));
return this;
}
get rootAccountName() {
return this.me.authKey.appId.toLowerCase();
}
onUpdateSubdomains(update) {
// Watch for subdomains appearing and disappearing
// noinspection JSCheckFunctionSignatures
return Promise.all(update['@delete'].map(subject => Promise.all(propertyValue(subject, 'subdomain', Array, Reference).map(tsRef => this.subdomainRemoved(this.ownedRefAsId(tsRef))))));
}
// TODO: implement this in timeld
onUpdateSubdomain(_id, _update, _state) {
return Promise.resolve();
}
/**
* @param sd
* @param iri gateway-relative or absolute IRI
* @param type note vocabulary is common between gw and ts
* @param key
*/
async writePrincipalToSubdomain(sd, iri, type, key) {
await sd.write({
'@id': this.absoluteId(iri),
'@type': type,
key: key.toJSON(true)
});
await sd.unlock();
}
getDataPath(id) {
return this.env.readyPath('data', 'domain', id.account, id.name);
}
async subdomainRemoved(id) {
try {
// Not relying on cache dispose, we want to wait for the close
await this.subdomainCache.peek(id.toDomain())?.close();
const path = await this.getDataPath(id);
// Remove the persistent data
await rm(path, { recursive: true, force: true });
// Write the tombstone file to prevent re-creation
await writeFile(`${path}.rip`, '');
// TODO: Remove all channel permissions
this.subdomainCache.del(id.toDomain());
LOG.info('Removed declared subdomain', id);
}
catch (e) {
LOG.warn('Error removing declared subdomain', id, e);
}
}
async tsTombstoneExists(id) {
const path = await this.getDataPath(id);
return access(`${path}.rip`).then(() => true, () => false);
}
async account(account, orCreate = false) {
let acc;
await this.domain.write(async (state) => {
const src = await state.get(account);
if (src != null) {
acc = Account.fromJSON(this, src);
}
else if (orCreate) {
acc = new Account(this, { name: account });
await state.write(acc.toJSON());
}
});
return acc;
}
/**
* @param user name
* @param email
*/
async activation(user, email) {
// If the account exists, check the email is registered
const acc = await this.account(user);
if (acc != null && !acc.emails.has(email))
throw new UnauthorizedError('Email %s not registered to account %s', email, user);
// Construct a JWT with the email, using our authorisation key
const { secret, keyid } = this.me.authKey;
const jwt = jsonwebtoken.sign({ email }, secret, {
keyid, expiresIn: '10m', subject: user
});
// Encrypt the JWT with the activation code
const code = randomInt(111111, 1000000).toString(10);
const jwe = new Cryptr(code).encrypt(jwt);
return { jwe, code };
}
/**
* Verify the JWT was an activation created by us
* @param code an activation code created by this Gateway
* @param jwe a corresponding encrypted JWT
*/
verifyActivation(code, jwe) {
const jwt = new Cryptr(code).decrypt(jwe);
const { sub, email } = jsonwebtoken.verify(jwt, this.me.authKey.secret);
if (!AccountOwnedId.isComponentId(sub))
throw new BadRequestError;
return { user: sub, email };
}
/**
* Gets the m-ld configuration for a subdomain. Calling this method will
* create the subdomain if it does not already exist.
*
* The caller must have already checked user access to the subdomain.
*
* @param spec the subdomain identity
* @param naming if `'any'`, a named subdomain will be created
* @param who the user who is asking
*/
async subdomainConfig(spec, naming, who) {
const id = this.ownedId(spec);
const remotesAuth = [];
// Use m-ld write locking to guard against API race conditions
await this.domain.write(async (state) => {
if (naming === 'any') {
// Does this subdomain already exist in its account?
const src = await state.get(id.toRelativeIri()); // Genesis if null
let sdClone = this.subdomainCache.get(id.toDomain());
if (sdClone == null) {
// Check that this subdomain has not existed before
if (src == null && await this.tsTombstoneExists(id))
throw new ConflictError('Cannot re-use domain name');
sdClone = await this.cloneSubdomain(spec, src == null);
// Ensure that the clone is online to avoid race with the client
await sdClone.clone.status.becomes({ online: true });
// Ensure the subdomain is in the domain
state = await state.write({
'@id': id.account, subdomain: sdClone.toJSON()
});
if (sdClone.useSignatures && who != null) {
// Ensure that the user account is in the subdomain for signing
const userKey = await who.acc.key(state, who.keyid);
await this.writePrincipalToSubdomain(sdClone, who.acc.name, 'Account', userKey);
}
this.subdomainCache.set(id.toDomain(), sdClone);
}
else if (src != null &&
spec.useSignatures != null &&
Subdomain.fromJSON(src).useSignatures !== spec.useSignatures) {
throw new ConflictError('Cannot change use of signatures after creation');
}
}
remotesAuth.push(...await Account.getDetails(state, spec.account, 'remotesAuth'));
});
// Return the config required for a new clone, using some of our config
return Object.assign({
'@domain': this.ownedId(spec).toDomain(), genesis: naming !== 'any'
}, await this.cloneFactory.reusableConfig(this.config, remotesAuth, who));
}
getSubdomain(id) {
return new Promise((resolve, reject) => {
// Use a read lock to prevent concurrent cache manipulation
this.domain.read(async (state) => {
try { // First check the cache
const inCache = this.subdomainCache.get(id.toDomain());
if (inCache == null) {
const src = await state.get(id.toRelativeIri());
if (src != null) {
LOG.debug('Cloning declared subdomain', id);
const sdc = await this.cloneSubdomain(Subdomain.fromJSON(src), false);
this.subdomainCache.set(id.toDomain(), sdc);
return resolve(sdc);
}
}
return resolve(inCache);
}
catch (e) {
reject(e);
}
});
});
}
async cloneSubdomain(spec, genesis = false) {
const id = this.ownedId(spec);
const config = Object.assign(Env.mergeConfig(this.config, {
'@id': uuid(), '@domain': id.toDomain(), '@context': false
}), { genesis });
LOG.info(id, 'ID is', config['@id']);
const sdc = new SubdomainClone(spec, ...await this.cloneFactory.clone(config, await this.getDataPath(id), spec.useSignatures ? this.me : undefined));
// Attach change listener
// Note we have not waited for up to date, so this picks up rev-ups
sdc.clone.follow((update, state) => this.onUpdateSubdomain(id, update, state));
if (sdc.useSignatures) {
// Add our machine identity and key to the subdomain for signing
await this.writePrincipalToSubdomain(sdc, '/', 'Gateway', this.me.userKey);
}
// We could put sdc in the cache here, but prefer to leave that to the
// caller so that cache calls are co-located.
return sdc;
}
/** @internal for tests */
hasClonedSubdomain(id) {
return this.subdomainCache.has(id.toDomain());
}
async close() {
this.subs.unsubscribe();
// Close the gateway domain
await Promise.all([
this.domain?.close(),
this.subdomainCache.clear()
]);
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiR2F0ZXdheS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2ZXIvR2F0ZXdheS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQzZDLGFBQWEsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUNqRixNQUFNLFlBQVksQ0FBQztBQUNwQixPQUFPLEVBQ0wsY0FBYyxFQUFFLFdBQVcsRUFBbUMsR0FBRyxFQUFFLGdCQUFnQixFQUNwRixNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFBRSxjQUFjLEVBQWdCLE1BQU0sa0JBQWtCLENBQUM7QUFDaEUsT0FBTyxHQUFHLE1BQU0sVUFBVSxDQUFDO0FBQzNCLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLFNBQVMsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNwRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ3BDLE9BQU8sRUFBRSxlQUFlLEVBQUUsYUFBYSxFQUFFLGlCQUFpQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFdEYsT0FBTyxFQUFFLE9BQU8sRUFBb0QsTUFBTSxjQUFjLENBQUM7QUFDekYsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3JELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDbkMsT0FBTyxZQUE0QixNQUFNLGNBQWMsQ0FBQztBQUN4RCxPQUFPLE1BQU0sTUFBTSxRQUFRLENBQUM7QUFDNUIsT0FBTyxFQUFFLFNBQVMsRUFBaUIsTUFBTSxzQkFBc0IsQ0FBQztBQVFoRSxNQUFNLE9BQU8sT0FBUSxTQUFRLFdBQVc7SUFPdEMsb0VBQW9FO0lBQ3BFLFlBQ21CLEdBQVEsRUFDekIsTUFBcUIsRUFDSixZQUEwQixFQUNsQyxRQUFrQixFQUNWLGNBQThCO1FBRS9DLEtBQUssQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFFLENBQUMsQ0FBQztRQU5ULFFBQUcsR0FBSCxHQUFHLENBQUs7UUFFUixpQkFBWSxHQUFaLFlBQVksQ0FBYztRQUNsQyxhQUFRLEdBQVIsUUFBUSxDQUFVO1FBQ1YsbUJBQWMsR0FBZCxjQUFjLENBQWdCO1FBUmhDLFNBQUksR0FBaUIsSUFBSSxZQUFZLEVBQUUsQ0FBQztRQVd2RCxHQUFHLENBQUMsS0FBSyxDQUFDLG1CQUFtQixFQUFFLElBQUksQ0FBQyxVQUFVLENBQUMsQ0FBQztRQUNoRCxJQUFJLENBQUMsTUFBTSxHQUFHLEVBQUUsR0FBRyxNQUFNLEVBQUUsVUFBVSxFQUFFLGNBQWMsRUFBRSxDQUFDO1FBQ3hELElBQUksQ0FBQyxFQUFFLEdBQUcsSUFBSSxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUNwRSxDQUFDO0lBRUQsS0FBSyxDQUFDLFVBQVU7UUFDZCwwQkFBMEI7UUFDMUIsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDdkQsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsTUFBTSxJQUFJLENBQUMsWUFBWSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxFQUFFLE9BQU8sQ0FBQyxDQUFDO1FBQ3BFLE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxDQUFDLENBQUM7UUFDdEQsK0RBQStEO1FBQy9ELE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQ3JCLElBQUksT0FBTyxDQUFDLElBQUksRUFBRTtZQUNoQixJQUFJLEVBQUUsSUFBSSxDQUFDLGVBQWUsRUFBRSxNQUFNLEVBQUUsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUM7U0FDNUQsQ0FBQyxDQUFDLE1BQU0sRUFBRSxDQUNaLENBQUM7UUFDRixJQUFJLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsRUFBRSxDQUN4QyxJQUFJLENBQUMsa0JBQWtCLENBQUMsTUFBTSxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDO1FBQzNDLE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELElBQUksZUFBZTtRQUNqQixPQUFPLElBQUksQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxXQUFXLEVBQUUsQ0FBQztJQUM3QyxDQUFDO0lBRUQsa0JBQWtCLENBQUMsTUFBa0I7UUFDbkMsa0RBQWtEO1FBQ2xELHlDQUF5QztRQUN6QyxPQUFPLE9BQU8sQ0FBQyxHQUFHLENBQ2hCLE1BQU0sQ0FBQyxTQUFTLENBQUMsQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUMxQyxhQUFhLENBQUMsT0FBTyxFQUFFLFdBQVcsRUFBRSxLQUFLLEVBQUUsU0FBUyxDQUFDLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQ2hFLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQ3ZELENBQUM7SUFDSixDQUFDO0lBRUQsaUNBQWlDO0lBQ2pDLGlCQUFpQixDQUNmLEdBQW1CLEVBQ25CLE9BQW1CLEVBQ25CLE1BQXFCO1FBRXJCLE9BQU8sT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQzNCLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNILEtBQUssQ0FBQyx5QkFBeUIsQ0FDN0IsRUFBa0IsRUFDbEIsR0FBUSxFQUNSLElBQTJCLEVBQzNCLEdBQVk7UUFFWixNQUFNLEVBQUUsQ0FBQyxLQUFLLENBQUM7WUFDYixLQUFLLEVBQUUsSUFBSSxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUM7WUFDM0IsT0FBTyxFQUFFLElBQUk7WUFDYixHQUFHLEVBQUUsR0FBRyxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUM7U0FDdEIsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxFQUFFLENBQUMsTUFBTSxFQUFFLENBQUM7SUFDcEIsQ0FBQztJQUVELFdBQVcsQ0FBQyxFQUFrQjtRQUM1QixPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsU0FBUyxDQUFDLE1BQU0sRUFBRSxRQUFRLEVBQUUsRUFBRSxDQUFDLE9BQU8sRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDbkUsQ0FBQztJQUVELEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFrQjtRQUN2QyxJQUFJO1lBQ0YsOERBQThEO1lBQzlELE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLFFBQVEsRUFBRSxDQUFDLEVBQUUsS0FBSyxFQUFFLENBQUM7WUFDdkQsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1lBQ3hDLDZCQUE2QjtZQUM3QixNQUFNLEVBQUUsQ0FBQyxJQUFJLEVBQUUsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO1lBQ2pELGtEQUFrRDtZQUNsRCxNQUFNLFNBQVMsQ0FBQyxHQUFHLElBQUksTUFBTSxFQUFFLEVBQUUsQ0FBQyxDQUFDO1lBQ25DLHVDQUF1QztZQUN2QyxJQUFJLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztZQUN2QyxHQUFHLENBQUMsSUFBSSxDQUFDLDRCQUE0QixFQUFFLEVBQUUsQ0FBQyxDQUFDO1NBQzVDO1FBQUMsT0FBTyxDQUFDLEVBQUU7WUFDVixHQUFHLENBQUMsSUFBSSxDQUFDLG1DQUFtQyxFQUFFLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQztTQUN0RDtJQUNILENBQUM7SUFFRCxLQUFLLENBQUMsaUJBQWlCLENBQUMsRUFBa0I7UUFDeEMsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ3hDLE9BQU8sTUFBTSxDQUFDLEdBQUcsSUFBSSxNQUFNLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUMsSUFBSSxFQUFFLEdBQUcsRUFBRSxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQzdELENBQUM7SUFRRCxLQUFLLENBQUMsT0FBTyxDQUFDLE9BQWUsRUFBRSxXQUFvQixLQUFLO1FBQ3RELElBQUksR0FBd0IsQ0FBQztRQUM3QixNQUFNLElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLEtBQUssRUFBQyxLQUFLLEVBQUMsRUFBRTtZQUNwQyxNQUFNLEdBQUcsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDckMsSUFBSSxHQUFHLElBQUksSUFBSSxFQUFFO2dCQUNmLEdBQUcsR0FBRyxPQUFPLENBQUMsUUFBUSxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQzthQUNuQztpQkFBTSxJQUFJLFFBQVEsRUFBRTtnQkFDbkIsR0FBRyxHQUFHLElBQUksT0FBTyxDQUFDLElBQUksRUFBRSxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsQ0FBQyxDQUFDO2dCQUMzQyxNQUFNLEtBQUssQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7YUFDakM7UUFDSCxDQUFDLENBQUMsQ0FBQztRQUNILE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQztJQUVEOzs7T0FHRztJQUNILEtBQUssQ0FBQyxVQUFVLENBQUMsSUFBWSxFQUFFLEtBQWE7UUFDMUMsdURBQXVEO1FBQ3ZELE1BQU0sR0FBRyxHQUFHLE1BQU0sSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNyQyxJQUFJLEdBQUcsSUFBSSxJQUFJLElBQUksQ0FBQyxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUM7WUFDdkMsTUFBTSxJQUFJLGlCQUFpQixDQUN6Qix1Q0FBdUMsRUFBRSxLQUFLLEVBQUUsSUFBSSxDQUFDLENBQUM7UUFDMUQsOERBQThEO1FBQzlELE1BQU0sRUFBRSxNQUFNLEVBQUUsS0FBSyxFQUFFLEdBQUcsSUFBSSxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUM7UUFDMUMsTUFBTSxHQUFHLEdBQUcsWUFBWSxDQUFDLElBQUksQ0FBQyxFQUFFLEtBQUssRUFBRSxFQUFFLE1BQU0sRUFBRTtZQUMvQyxLQUFLLEVBQUUsU0FBUyxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsSUFBSTtTQUN2QyxDQUFDLENBQUM7UUFDSCwyQ0FBMkM7UUFDM0MsTUFBTSxJQUFJLEdBQUcsU0FBUyxDQUFDLE1BQU0sRUFBRSxPQUFPLENBQUMsQ0FBQyxRQUFRLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDckQsTUFBTSxHQUFHLEdBQUcsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzFDLE9BQU8sRUFBRSxHQUFHLEVBQUUsSUFBSSxFQUFFLENBQUM7SUFDdkIsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxnQkFBZ0IsQ0FBQyxJQUFZLEVBQUUsR0FBVztRQUN4QyxNQUFNLEdBQUcsR0FBRyxJQUFJLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDMUMsTUFBTSxFQUFFLEdBQUcsRUFBRSxLQUFLLEVBQUUsR0FDbEIsWUFBWSxDQUFDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLEVBQUUsQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFlLENBQUM7UUFDakUsSUFBSSxDQUFDLGNBQWMsQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDO1lBQ3BDLE1BQU0sSUFBSSxlQUFlLENBQUM7UUFDNUIsT0FBTyxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDOUIsQ0FBQztJQUVEOzs7Ozs7Ozs7T0FTRztJQUNILEtBQUssQ0FBQyxlQUFlLENBQ25CLElBQW1CLEVBQ25CLE1BQXVCLEVBQ3ZCLEdBQVM7UUFFVCxNQUFNLEVBQUUsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQzlCLE1BQU0sV0FBVyxHQUFzQixFQUFFLENBQUM7UUFDMUMsOERBQThEO1FBQzlELE1BQU0sSUFBSSxDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsS0FBSyxFQUFDLEtBQUssRUFBQyxFQUFFO1lBQ3BDLElBQUksTUFBTSxLQUFLLEtBQUssRUFBRTtnQkFDcEIsb0RBQW9EO2dCQUNwRCxNQUFNLEdBQUcsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLGFBQWEsRUFBRSxDQUFDLENBQUMsQ0FBQyxrQkFBa0I7Z0JBQ25FLElBQUksT0FBTyxHQUFHLElBQUksQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO2dCQUNyRCxJQUFJLE9BQU8sSUFBSSxJQUFJLEVBQUU7b0JBQ25CLG1EQUFtRDtvQkFDbkQsSUFBSSxHQUFHLElBQUksSUFBSSxJQUFJLE1BQU0sSUFBSSxDQUFDLGlCQUFpQixDQUFDLEVBQUUsQ0FBQzt3QkFDakQsTUFBTSxJQUFJLGFBQWEsQ0FBQywyQkFBMkIsQ0FBQyxDQUFDO29CQUN2RCxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLElBQUksRUFBRSxHQUFHLElBQUksSUFBSSxDQUFDLENBQUM7b0JBQ3ZELGdFQUFnRTtvQkFDaEUsTUFBTSxPQUFPLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztvQkFDckQsd0NBQXdDO29CQUN4QyxLQUFLLEdBQUcsTUFBTSxLQUFLLENBQUMsS0FBSyxDQUFDO3dCQUN4QixLQUFLLEVBQUUsRUFBRSxDQUFDLE9BQU8sRUFBRSxTQUFTLEVBQUUsT0FBTyxDQUFDLE1BQU0sRUFBRTtxQkFDL0MsQ0FBQyxDQUFDO29CQUNILElBQUksT0FBTyxDQUFDLGFBQWEsSUFBSSxHQUFHLElBQUksSUFBSSxFQUFFO3dCQUN4QywrREFBK0Q7d0JBQy9ELE1BQU0sT0FBTyxHQUFHLE1BQU0sR0FBRyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQzt3QkFDcEQsTUFBTSxJQUFJLENBQUMseUJBQXlCLENBQ2xDLE9BQU8sRUFBRSxHQUFHLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxTQUFTLEVBQUUsT0FBTyxDQUFDLENBQUM7cUJBQzlDO29CQUNELElBQUksQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUUsRUFBRSxPQUFPLENBQUMsQ0FBQztpQkFDakQ7cUJBQU0sSUFBSSxHQUFHLElBQUksSUFBSTtvQkFDcEIsSUFBSSxDQUFDLGFBQWEsSUFBSSxJQUFJO29CQUMxQixTQUFTLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxDQUFDLGFBQWEsS0FBSyxJQUFJLENBQUMsYUFBYSxFQUFFO29CQUM5RCxNQUFNLElBQUksYUFBYSxDQUFDLGdEQUFnRCxDQUFDLENBQUM7aUJBQzNFO2FBQ0Y7WUFDRCxXQUFXLENBQUMsSUFBSSxDQUFDLEdBQUcsTUFBTSxPQUFPLENBQUMsVUFBVSxDQUMxQyxLQUFLLEVBQUUsSUFBSSxDQUFDLE9BQU8sRUFBRSxhQUFhLENBQUMsQ0FBQyxDQUFDO1FBQ3pDLENBQUMsQ0FBQyxDQUFDO1FBQ0gsdUVBQXVFO1FBQ3ZFLE9BQU8sTUFBTSxDQUFDLE1BQU0sQ0FBQztZQUNuQixTQUFTLEVBQUUsSUFBSSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxRQUFRLEVBQUUsRUFBRSxPQUFPLEVBQUUsTUFBTSxLQUFLLEtBQUs7U0FDcEUsRUFBRSxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsV0FBVyxFQUFFLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDNUUsQ0FBQztJQUVELFlBQVksQ0FBQyxFQUFrQjtRQUM3QixPQUFPLElBQUksT0FBTyxDQUE2QixDQUFDLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRTtZQUNqRSwyREFBMkQ7WUFDM0QsSUFBSSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFDLEtBQUssRUFBQyxFQUFFO2dCQUM3QixJQUFJLEVBQUUsd0JBQXdCO29CQUM1QixNQUFNLE9BQU8sR0FBRyxJQUFJLENBQUMsY0FBYyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztvQkFDdkQsSUFBSSxPQUFPLElBQUksSUFBSSxFQUFFO3dCQUNuQixNQUFNLEdBQUcsR0FBRyxNQUFNLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLGFBQWEsRUFBRSxDQUFDLENBQUM7d0JBQ2hELElBQUksR0FBRyxJQUFJLElBQUksRUFBRTs0QkFDZixHQUFHLENBQUMsS0FBSyxDQUFDLDRCQUE0QixFQUFFLEVBQUUsQ0FBQyxDQUFDOzRCQUM1QyxNQUFNLEdBQUcsR0FBRyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQzs0QkFDdEUsSUFBSSxDQUFDLGNBQWMsQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLFFBQVEsRUFBRSxFQUFFLEdBQUcsQ0FBQyxDQUFDOzRCQUM1QyxPQUFPLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQzt5QkFDckI7cUJBQ0Y7b0JBQ0QsT0FBTyxPQUFPLENBQUMsT0FBTyxDQUFDLENBQUM7aUJBQ3pCO2dCQUFDLE9BQU8sQ0FBQyxFQUFFO29CQUNWLE1BQU0sQ0FBQyxDQUFDLENBQUMsQ0FBQztpQkFDWDtZQUNILENBQUMsQ0FBQyxDQUFDO1FBQ0wsQ0FBQyxDQUFDLENBQUM7SUFDTCxDQUFDO0lBRU8sS0FBSyxDQUFDLGNBQWMsQ0FBQyxJQUFtQixFQUFFLE9BQU8sR0FBRyxLQUFLO1FBQy9ELE1BQU0sRUFBRSxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDOUIsTUFBTSxNQUFNLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFvQixJQUFJLENBQUMsTUFBTSxFQUFFO1lBQzNFLEtBQUssRUFBRSxJQUFJLEVBQUUsRUFBRSxTQUFTLEVBQUUsRUFBRSxDQUFDLFFBQVEsRUFBRSxFQUFFLFVBQVUsRUFBRSxLQUFLO1NBQzNELENBQUMsRUFBRSxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7UUFDakIsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLEVBQUUsT0FBTyxFQUFFLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDO1FBQ3JDLE1BQU0sR0FBRyxHQUFHLElBQUksY0FBYyxDQUFDLElBQUksRUFBRSxHQUFHLE1BQU0sSUFBSSxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQ25FLE1BQU0sRUFBRSxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDLEVBQUUsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUM3RSxDQUFDLENBQUM7UUFDSCx5QkFBeUI7UUFDekIsbUVBQW1FO1FBQ25FLEdBQUcsQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxFQUFFLEtBQUssRUFBRSxFQUFFLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLEVBQUUsRUFBRSxNQUFNLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUMvRSxJQUFJLEdBQUcsQ0FBQyxhQUFhLEVBQUU7WUFDckIsZ0VBQWdFO1lBQ2hFLE1BQU0sSUFBSSxDQUFDLHlCQUF5QixDQUFDLEdBQUcsRUFBRSxHQUFHLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDLENBQUM7U0FDNUU7UUFDRCxzRUFBc0U7UUFDdEUsNkNBQTZDO1FBQzdDLE9BQU8sR0FBRyxDQUFDO0lBQ2IsQ0FBQztJQUVELDBCQUEwQjtJQUMxQixrQkFBa0IsQ0FBQyxFQUFrQjtRQUNuQyxPQUFPLElBQUksQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO0lBQ2hELENBQUM7SUFFRCxLQUFLLENBQUMsS0FBSztRQUNULElBQUksQ0FBQyxJQUFJLENBQUMsV0FBVyxFQUFFLENBQUM7UUFDeEIsMkJBQTJCO1FBQzNCLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQztZQUNoQixJQUFJLENBQUMsTUFBTSxFQUFFLEtBQUssRUFBRTtZQUNwQixJQUFJLENBQUMsY0FBYyxDQUFDLEtBQUssRUFBRTtTQUM1QixDQUFDLENBQUM7SUFDTCxDQUFDO0NBQ0YifQ==