@m-ld/gateway
Version:
m-ld gateway for services
196 lines • 18.1 kB
JavaScript
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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