UNPKG

@m-ld/gateway

Version:

m-ld gateway for services

238 lines 23 kB
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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiR2F0ZXdheS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2ZXIvR2F0ZXdheS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQzZDLGFBQWEsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUNqRixNQUFNLFlBQVksQ0FBQztBQUNwQixPQUFPLEVBQ0wsY0FBYyxFQUFFLFdBQVcsRUFBbUMsR0FBRyxFQUFFLGdCQUFnQixFQUNwRixNQUFNLGlCQUFpQixDQUFDO0FBQ3pCLE9BQU8sRUFBRSxjQUFjLEVBQWdCLE1BQU0sa0JBQWtCLENBQUM7QUFDaEUsT0FBTyxHQUFHLE1BQU0sVUFBVSxDQUFDO0FBQzNCLE9BQU8sRUFBRSxNQUFNLEVBQUUsRUFBRSxFQUFFLFNBQVMsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNwRCxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQ3BDLE9BQU8sRUFBRSxlQUFlLEVBQUUsYUFBYSxFQUFFLGlCQUFpQixFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFFdEYsT0FBTyxFQUFFLE9BQU8sRUFBb0QsTUFBTSxjQUFjLENBQUM7QUFDekYsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLHFCQUFxQixDQUFDO0FBQ3JELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxRQUFRLENBQUM7QUFDbkMsT0FBTyxZQUE0QixNQUFNLGNBQWMsQ0FBQztBQUN4RCxPQUFPLE1BQU0sTUFBTSxRQUFRLENBQUM7QUFDNUIsT0FBTyxFQUFFLFNBQVMsRUFBaUIsTUFBTSxzQkFBc0IsQ0FBQztBQVFoRSxNQUFNLE9BQU8sT0FBUSxTQUFRLFdBQVc7SUFPdEMsb0VBQW9FO0lBQ3BFLFlBQ21CLEdBQVEsRUFDekIsTUFBcUIsRUFDSixZQUEwQixFQUNsQyxRQUFrQixFQUNWLGNBQThCO1FBRS9DLEtBQUssQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFFLENBQUMsQ0FBQztRQU5ULFFBQUcsR0FBSCxHQUFH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