diff --git a/.gitignore b/.gitignore index 1b618d0e2..12b7a8dcc 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ tmp proto/build /dist !/drizzle/**/*.sql +.eslintcache diff --git a/src/capabilities.js b/src/capabilities.js index 64d8345f9..05d8b4485 100644 --- a/src/capabilities.js +++ b/src/capabilities.js @@ -199,6 +199,44 @@ export class Capabilities { return capabilities } + /** + * Get capabilities of all devices in the project. For your own device, if you + * have not yet synced your own role record, the "no role" capabilties is + * returned. The project creator will have `CREATOR_CAPABILITIES` unless a + * different role has been assigned. + * + * @returns {Promise>} Map of deviceId to Capability + */ + async getAll() { + const roles = await this.#dataType.getMany() + let projectCreatorDeviceId + try { + projectCreatorDeviceId = await this.#coreOwnership.getOwner( + this.#projectCreatorAuthCoreId + ) + } catch (e) { + // Not found, we don't know who the project creator is so we can't include + // them in the returned map + } + /** @type {Record} */ + const capabilities = {} + for (const role of roles) { + const deviceId = role.docId + if (!isKnownRoleId(role.roleId)) continue + capabilities[deviceId] = DEFAULT_CAPABILITIES[role.roleId] + } + const includesSelf = Boolean(capabilities[this.#ownDeviceId]) + if (!includesSelf) { + const isProjectCreator = this.#ownDeviceId === projectCreatorDeviceId + if (isProjectCreator) { + capabilities[this.#ownDeviceId] = CREATOR_CAPABILITIES + } else { + capabilities[this.#ownDeviceId] = NO_ROLE_CAPABILITIES + } + } + return capabilities + } + /** * Assign a role to the specified `deviceId`. Devices without an assigned role * are unable to sync, except the project creator that defaults to having all diff --git a/test-e2e/capabilities.js b/test-e2e/capabilities.js index 3192c85e8..7019857fa 100644 --- a/test-e2e/capabilities.js +++ b/test-e2e/capabilities.js @@ -7,6 +7,8 @@ import { DEFAULT_CAPABILITIES, CREATOR_CAPABILITIES, MEMBER_ROLE_ID, + COORDINATOR_ROLE_ID, + NO_ROLE_CAPABILITIES, } from '../src/capabilities.js' import { randomBytes } from 'crypto' @@ -72,3 +74,69 @@ test('New device without capabilities', async (t) => { await project[kCapabilities].assignRole(deviceId, MEMBER_ROLE_ID) }, 'Trying to assign a role without capabilities throws an error') }) + +test('getMany() - on invitor device', async (t) => { + const rootKey = KeyManager.generateRootKey() + const km = new KeyManager(rootKey) + const creatorDeviceId = km.getIdentityKeypair().publicKey.toString('hex') + const manager = new MapeoManager({ + rootKey, + dbFolder: ':memory:', + coreStorage: () => new RAM(), + }) + + const projectId = await manager.createProject() + const project = await manager.getProject(projectId) + const ownCapabilities = await project.$getOwnCapabilities() + + t.alike( + ownCapabilities, + CREATOR_CAPABILITIES, + 'Project creator has creator capabilities' + ) + + const deviceId1 = randomBytes(32).toString('hex') + const deviceId2 = randomBytes(32).toString('hex') + await project[kCapabilities].assignRole(deviceId1, MEMBER_ROLE_ID) + await project[kCapabilities].assignRole(deviceId2, COORDINATOR_ROLE_ID) + + const expected = { + [deviceId1]: DEFAULT_CAPABILITIES[MEMBER_ROLE_ID], + [deviceId2]: DEFAULT_CAPABILITIES[COORDINATOR_ROLE_ID], + [creatorDeviceId]: CREATOR_CAPABILITIES, + } + + t.alike( + await project[kCapabilities].getAll(), + expected, + 'expected capabilities' + ) +}) + +test('getMany() - on newly invited device before sync', async (t) => { + const rootKey = KeyManager.generateRootKey() + const km = new KeyManager(rootKey) + const deviceId = km.getIdentityKeypair().publicKey.toString('hex') + const manager = new MapeoManager({ + rootKey, + dbFolder: ':memory:', + coreStorage: () => new RAM(), + }) + + const projectId = await manager.addProject({ + projectKey: randomBytes(32), + encryptionKeys: { auth: randomBytes(32) }, + }) + const project = await manager.getProject(projectId) + await project.ready() + + const expected = { + [deviceId]: NO_ROLE_CAPABILITIES, + } + + t.alike( + await project[kCapabilities].getAll(), + expected, + 'expected capabilities' + ) +})