UNPKG

@m-ld/gateway

Version:

m-ld gateway for services

196 lines 18.1 kB
import { propertyValue, Reference } from '@m-ld/m-ld'; import { AccountOwnedId, as, idSet, validate } from '../lib/index.js'; import { UserKey } from '../data/index.js'; import { BadRequestError, ForbiddenError, InternalServerError, UnauthorizedError } from '../http/errors.js'; import { userIsAdmin } from './statements.js'; const asAccountUpdate = as.object({ email: as.string().email(), naming: as.valid('any', 'uuid'), remotesAuth: as.valid('anon', 'key', 'jwt') }).or('email', 'naming', 'remotesAuth'); /** * Javascript representation of an Account subject in the Gateway domain. * Instances are ephemeral, instantiated dynamically on demand. */ export class Account { static fromJSON(gateway, src) { // noinspection JSCheckFunctionSignatures return new Account(gateway, { name: src['@id'], emails: propertyValue(src, 'email', Set, String), keyids: propertyValue(src, 'key', Array, Reference).map(UserKey.keyidFromRef), admins: idSet(propertyValue(src, 'vf:primaryAccountable', Array, Reference)), subdomains: propertyValue(src, 'subdomain', Array, Reference) }); } static async getDetails(state, account, detail) { const acc = await state.get(account, detail); return acc != null ? propertyValue(acc, detail, Array, String) : []; } constructor(gateway, { name, emails = [], keyids = [], admins = [], subdomains = [] }) { this.gateway = gateway; this.gateway = gateway; this.name = name; this.emails = new Set([...emails ?? []]); this.keyids = new Set([...keyids ?? []]); this.admins = new Set([...admins ?? []]); this.subdomains = subdomains ?? []; this.allOwned = {}; // See allOwned } update(patch) { const asMyUpdate = asAccountUpdate.append({ '@id': as.equal(this.name).default(this.name) }); const update = validate(patch, as.object({ '@delete': asMyUpdate, '@insert': asMyUpdate }).or('@delete', '@insert')); return this.gateway.domain.write(update); } /** * Activation of a gateway account with a user email. * @returns key config for the account */ async generateKey(opts) { // Every activation creates a new key (assumes new device) const keyDetails = await this.gateway.keyStore .mintKey(`${this.name}@${this.gateway.domainName}`); // Generate a key pair for signing const userKey = UserKey.generate(keyDetails.key); const key = userKey.toJSON(); const config = opts.type != null ? userKey.toConfig(keyDetails.key) : keyDetails.key.toConfig(); // Store the keyid and the email this.keyids.add(keyDetails.key.keyid); if (opts.email) this.emails.add(opts.email); // Patch the changed details, including the new key await this.gateway.domain.write({ '@id': this.name, key, email: opts.email }); return config; } /** * TODO: Refactor awkward return type * @returns the user key, if the gateway is using user keys * @throws UnauthorizedError if the key does not belong to this account or is * revoked */ async authorise(keyid, access) { return new Promise(async (resolve, reject) => { this.gateway.domain.read(async (state) => { try { const userKey = await this.key(state, keyid); if (userKey.revoked) return reject(new UnauthorizedError('Key revoked')); if (access != null && !await this.hasAccess(state, access)) return reject(new ForbiddenError); // Special case: do not ping the key if it's the root if (this.name === this.gateway.rootAccountName) return resolve(this.gateway.me.userKey); const keyDetail = await this.gateway.keyStore.pingKey(keyid, () => this.allSubdomainIds(state)); if (keyDetail?.revoked) // TODO: If keystore says revoked, update the userKey return reject(new UnauthorizedError('Key revoked')); return resolve(userKey); } catch (e) { // TODO: Assuming this is a Not Found return reject(new UnauthorizedError(e)); } }); }); } async hasAccess(state, access) { const iri = access.id.toRelativeIri(); const writable = {}; if (access.forWrite && !this.ownedTypes.includes(access.forWrite)) throw new BadRequestError(`Not a recognised type: ${access.forWrite}`); for (let ownedType of this.ownedTypes) writable[ownedType] = await this.loadAllOwned(state, ownedType); if (access.forWrite && !(await state.ask({ '@where': { '@id': iri } }))) { // Creating; check write access to account if (!await this.hasWriteAccess(state, access.id.account)) return false; // Otherwise OK to create writable[access.forWrite].add(iri); return true; } else if (!Object.values(writable).some(owned => owned.has(iri))) { return access.forWrite ? false : this.hasReadAccess(state, iri, writable); } else { return true; } } async hasWriteAccess(state, toAccount) { return toAccount === this.name || state.ask({ '@where': userIsAdmin(this.name, toAccount) }); } // TODO: Override in timeld async hasReadAccess(_state, _iri, _writable) { return false; } /** * Override to support additional owned subject types. Each type must be used * as follows: * 1. As the `@type` of each owned subject * 2. Lower-cased, as a multi-valued property (from the gateway vocabulary) of * each Account * TODO: Override in timeld */ get ownedTypes() { return ['Subdomain']; } async loadAllOwned(state, type) { if (this.allOwned[type] == null) { this.allOwned[type] = type === 'Subdomain' ? idSet(this.subdomains) : new Set; await state.read({ '@select': '?owned', '@where': ({ '@type': 'Account', 'vf:primaryAccountable': { '@id': this.name }, [type.toLowerCase()]: '?owned' }) }).forEach(result => this.allOwned[type].add(result['?owned']['@id'])); } return this.allOwned[type]; } /** * Checks that the given keyid belongs to this account and returns the * corresponding user key * @throws UnauthorizedError if the key does not belong to this account */ async key(state, keyid) { if (!this.keyids.has(keyid)) throw new UnauthorizedError(`Key ${keyid} does not belong to account ${this.name}`); if (this.name === this.gateway.rootAccountName) return this.gateway.me.userKey; const src = await state.get(UserKey.refFromKeyid(keyid)['@id']); if (src != null) return UserKey.fromJSON(src); else throw new InternalServerError('User key not found'); } /** * @param {MeldReadState} state * @returns {Promise<AccountOwnedId[]>} */ async allSubdomainIds(state) { return [...await this.loadAllOwned(state, 'Subdomain')] .map(iri => AccountOwnedId.fromIri(iri, this.gateway.domainName)); } toJSON() { return { '@id': this.name, '@type': 'Account', 'email': [...this.emails], 'key': [...this.keyids].map(keyid => UserKey.refFromKeyid(keyid)), 'vf:primaryAccountable': [...this.admins].map(iri => ({ '@id': iri })), 'subdomain': this.subdomains }; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiQWNjb3VudC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2ZXIvQWNjb3VudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ21DLGFBQWEsRUFBRSxTQUFTLEVBQ2pFLE1BQU0sWUFBWSxDQUFDO0FBQ3BCLE9BQU8sRUFBRSxjQUFjLEVBQUUsRUFBRSxFQUFvQixLQUFLLEVBQVksUUFBUSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDbEcsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBQzNDLE9BQU8sRUFDTCxlQUFlLEVBQUUsY0FBYyxFQUFFLG1CQUFtQixFQUFFLGlCQUFpQixFQUN4RSxNQUFNLG1CQUFtQixDQUFDO0FBRTNCLE9BQU8sRUFBRSxXQUFXLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQTRCOUMsTUFBTSxlQUFlLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQztJQUNoQyxLQUFLLEVBQUUsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDLEtBQUssRUFBRTtJQUMxQixNQUFNLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDO0lBQy9CLFdBQVcsRUFBRSxFQUFFLENBQUMsS0FBSyxDQUFDLE1BQU0sRUFBRSxLQUFLLEVBQUUsS0FBSyxDQUFDO0NBQzVDLENBQUMsQ0FBQyxFQUFFLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxhQUFhLENBQUMsQ0FBQztBQUV4Qzs7O0dBR0c7QUFDSCxNQUFNLE9BQU8sT0FBTztJQUNsQixNQUFNLENBQUMsUUFBUSxDQUFDLE9BQXVCLEVBQUUsR0FBaUI7UUFDeEQseUNBQXlDO1FBQ3pDLE9BQU8sSUFBSSxPQUFPLENBQUMsT0FBTyxFQUFFO1lBQzFCLElBQUksRUFBRSxHQUFHLENBQUMsS0FBSyxDQUFDO1lBQ2hCLE1BQU0sRUFBRSxhQUFhLENBQUMsR0FBRyxFQUFFLE9BQU8sRUFBRSxHQUFHLEVBQUUsTUFB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