diff --git a/.eslintrc.json b/.eslintrc.json --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,8 @@ { "root": true, "env": { - "es6": true + "es6": true, + "es2020": true }, "extends": [ "eslint:recommended", diff --git a/keyserver/src/responders/responder-validators.test.js b/keyserver/src/responders/responder-validators.test.js --- a/keyserver/src/responders/responder-validators.test.js +++ b/keyserver/src/responders/responder-validators.test.js @@ -96,7 +96,6 @@ role: '83796', permissions: { know_of: { value: true, source: '1' }, - membership: { value: false, source: null }, visible: { value: true, source: '1' }, voiced: { value: true, source: '1' }, edit_entries: { value: true, source: '1' }, @@ -136,7 +135,6 @@ role: '83795', permissions: { know_of: { value: true, source: '1' }, - membership: { value: false, source: null }, visible: { value: true, source: '1' }, voiced: { value: false, source: null }, edit_entries: { value: false, source: null }, @@ -227,7 +225,6 @@ role: '83796', permissions: { know_of: { value: true, source: '1' }, - membership: { value: false, source: null }, visible: { value: true, source: '1' }, voiced: { value: true, source: '1' }, edit_entries: { value: true, source: '1' }, @@ -253,7 +250,6 @@ role: '83795', permissions: { know_of: { value: true, source: '1' }, - membership: { value: false, source: null }, visible: { value: true, source: '1' }, voiced: { value: false, source: null }, edit_entries: { value: false, source: null }, @@ -331,7 +327,6 @@ role: '83795', permissions: { know_of: { value: true, source: '1' }, - membership: { value: false, source: null }, visible: { value: true, source: '1' }, voiced: { value: false, source: null }, edit_entries: { value: false, source: null }, diff --git a/lib/permissions/minimally-encoded-thread-permissions.js b/lib/permissions/minimally-encoded-thread-permissions.js new file mode 100644 --- /dev/null +++ b/lib/permissions/minimally-encoded-thread-permissions.js @@ -0,0 +1,78 @@ +// @flow + +import invariant from 'invariant'; + +import type { + ThreadPermission, + ThreadPermissionsInfo, +} from '../types/thread-permission-types.js'; +import { entries } from '../utils/objects.js'; + +const minimallyEncodedThreadPermissions = Object.freeze({ + // TODO (atul): Update flow to `194.0.0` for bigint support + // $FlowIssue bigint-unsupported + know_of: BigInt(1) << BigInt(0), + visible: BigInt(1) << BigInt(1), + voiced: BigInt(1) << BigInt(2), + edit_entries: BigInt(1) << BigInt(3), + edit_thread: BigInt(1) << BigInt(4), // EDIT_THREAD_NAME + edit_thread_description: BigInt(1) << BigInt(5), + edit_thread_color: BigInt(1) << BigInt(6), + delete_thread: BigInt(1) << BigInt(7), + create_subthreads: BigInt(1) << BigInt(8), // CREATE_SUBCHANNELS + create_sidebars: BigInt(1) << BigInt(9), + join_thread: BigInt(1) << BigInt(10), + edit_permissions: BigInt(1) << BigInt(11), + add_members: BigInt(1) << BigInt(12), + remove_members: BigInt(1) << BigInt(13), + change_role: BigInt(1) << BigInt(14), + leave_thread: BigInt(1) << BigInt(15), + react_to_message: BigInt(1) << BigInt(16), + edit_message: BigInt(1) << BigInt(17), + edit_thread_avatar: BigInt(1) << BigInt(18), + manage_pins: BigInt(1) << BigInt(19), + manage_invite_links: BigInt(1) << BigInt(20), +}); + +// TODO (atul): Update flow to `194.0.0` for bigint support +// $FlowIssue bigint-unsupported +const permissionsToBitmask = (permissions: ThreadPermissionsInfo): bigint => { + let bitmask = BigInt(0); + for (const [key, permission] of entries(permissions)) { + if (permission.value && key in minimallyEncodedThreadPermissions) { + invariant( + // TODO (atul): Update flow to `194.0.0` for bigint support + // $FlowIssue illegal-typeof + typeof minimallyEncodedThreadPermissions[key] === 'bigint', + 'must be bigint', + ); + bitmask |= minimallyEncodedThreadPermissions[key]; + } + } + return bitmask; +}; + +const hasPermission = ( + // TODO (atul): Update flow to `194.0.0` for bigint support + // $FlowIssue bigint-unsupported + permissionsBitmask: bigint, + permission: ThreadPermission, +): boolean => { + if (!(permission in minimallyEncodedThreadPermissions)) { + return false; + } + const permissionBitmask = minimallyEncodedThreadPermissions[permission]; + invariant( + // TODO (atul): Update flow to `194.0.0` for bigint support + // $FlowIssue illegal-typeof + typeof permissionBitmask === 'bigint', + 'permissionBitmask must be of type bigint', + ); + return (permissionsBitmask & permissionBitmask) !== BigInt(0); +}; + +export { + minimallyEncodedThreadPermissions, + permissionsToBitmask, + hasPermission, +}; diff --git a/lib/permissions/minimally-encoded-thread-permissions.test.js b/lib/permissions/minimally-encoded-thread-permissions.test.js new file mode 100644 --- /dev/null +++ b/lib/permissions/minimally-encoded-thread-permissions.test.js @@ -0,0 +1,53 @@ +// @flow + +import { + hasPermission, + permissionsToBitmask, +} from './minimally-encoded-thread-permissions.js'; + +describe('minimallyEncodedThreadPermissions', () => { + const permissions = { + know_of: { value: true, source: '1' }, + visible: { value: true, source: '1' }, + voiced: { value: true, source: '1' }, + edit_entries: { value: true, source: '1' }, + edit_thread: { value: true, source: '1' }, + edit_thread_description: { value: true, source: '1' }, + edit_thread_color: { value: true, source: '1' }, + delete_thread: { value: true, source: '1' }, + create_subthreads: { value: true, source: '1' }, + create_sidebars: { value: true, source: '1' }, + join_thread: { value: false, source: null }, + edit_permissions: { value: false, source: null }, + add_members: { value: true, source: '1' }, + remove_members: { value: true, source: '1' }, + change_role: { value: true, source: '1' }, + leave_thread: { value: false, source: null }, + react_to_message: { value: true, source: '1' }, + edit_message: { value: true, source: '1' }, + }; + + it('should encode ThreadPermissionsInfo as bitmask', () => { + const permissionsBitmask = permissionsToBitmask(permissions); + expect(permissionsBitmask.toString(2)).toBe('110111001111111111'); + expect(hasPermission(permissionsBitmask, 'know_of')).toBe(true); + expect(hasPermission(permissionsBitmask, 'visible')).toBe(true); + expect(hasPermission(permissionsBitmask, 'voiced')).toBe(true); + expect(hasPermission(permissionsBitmask, 'edit_entries')).toBe(true); + expect(hasPermission(permissionsBitmask, 'edit_thread')).toBe(true); + expect(hasPermission(permissionsBitmask, 'edit_thread_description')).toBe( + true, + ); + expect(hasPermission(permissionsBitmask, 'edit_thread_color')).toBe(true); + expect(hasPermission(permissionsBitmask, 'delete_thread')).toBe(true); + expect(hasPermission(permissionsBitmask, 'create_subthreads')).toBe(true); + expect(hasPermission(permissionsBitmask, 'create_sidebars')).toBe(true); + expect(hasPermission(permissionsBitmask, 'join_thread')).toBe(false); + expect(hasPermission(permissionsBitmask, 'edit_permissions')).toBe(false); + expect(hasPermission(permissionsBitmask, 'remove_members')).toBe(true); + expect(hasPermission(permissionsBitmask, 'change_role')).toBe(true); + expect(hasPermission(permissionsBitmask, 'leave_thread')).toBe(false); + expect(hasPermission(permissionsBitmask, 'react_to_message')).toBe(true); + expect(hasPermission(permissionsBitmask, 'edit_message')).toBe(true); + }); +}); diff --git a/lib/types/thread-permission-types.js b/lib/types/thread-permission-types.js --- a/lib/types/thread-permission-types.js +++ b/lib/types/thread-permission-types.js @@ -12,7 +12,6 @@ // across all roles, it should be added to `universalCommunityPermissions`. export const threadPermissions = Object.freeze({ KNOW_OF: 'know_of', - MEMBERSHIP_DEPRECATED: 'membership', VISIBLE: 'visible', VOICED: 'voiced', EDIT_ENTRIES: 'edit_entries', @@ -41,7 +40,6 @@ ): ThreadPermission { invariant( ourThreadPermissions === 'know_of' || - ourThreadPermissions === 'membership' || ourThreadPermissions === 'visible' || ourThreadPermissions === 'voiced' || ourThreadPermissions === 'edit_entries' || diff --git a/lib/types/validation.test.js b/lib/types/validation.test.js --- a/lib/types/validation.test.js +++ b/lib/types/validation.test.js @@ -363,10 +363,6 @@ value: true, source: '1', }, - membership: { - value: false, - source: null, - }, visible: { value: true, source: '1', @@ -450,10 +446,6 @@ value: true, source: '85171', }, - membership: { - value: false, - source: null, - }, visible: { value: true, source: '85171', @@ -559,10 +551,6 @@ value: true, source: '85171', }, - membership: { - value: false, - source: null, - }, visible: { value: true, source: '85171', diff --git a/lib/utils/thread-ops-utils.test.js b/lib/utils/thread-ops-utils.test.js --- a/lib/utils/thread-ops-utils.test.js +++ b/lib/utils/thread-ops-utils.test.js @@ -26,10 +26,6 @@ value: true, source: '1', }, - membership: { - value: false, - source: null, - }, visible: { value: true, source: '1', @@ -109,10 +105,6 @@ value: true, source: '84015', }, - membership: { - value: false, - source: null, - }, visible: { value: true, source: '84015', @@ -192,10 +184,6 @@ value: true, source: '84015', }, - membership: { - value: false, - source: null, - }, visible: { value: true, source: '84015', @@ -297,10 +285,6 @@ value: true, source: '84015', }, - membership: { - value: false, - source: null, - }, visible: { value: true, source: '84015', @@ -391,11 +375,11 @@ creationTime: '1679595843051', parentThreadID: '1', members: - '[{"id":"256","role":null,"permissions":{"know_of":{"value":true,"source":"1"},"membership":{"value":false,"source":null},"visible":{"value":true,"source":"1"},"voiced":{"value":true,"source":"1"},"edit_entries":{"value":true,"source":"1"},"edit_thread":{"value":true,"source":"1"},"edit_thread_description":{"value":true,"source":"1"},"edit_thread_color":{"value":true,"source":"1"},"delete_thread":{"value":true,"source":"1"},"create_subthreads":{"value":true,"source":"1"},"create_sidebars":{"value":true,"source":"1"},"join_thread":{"value":true,"source":"1"},"edit_permissions":{"value":true,"source":"1"},"add_members":{"value":true,"source":"1"},"remove_members":{"value":true,"source":"1"},"change_role":{"value":true,"source":"1"},"leave_thread":{"value":false,"source":null},"react_to_message":{"value":false,"source":null},"edit_message":{"value":false,"source":null}},"isSender":false},{"id":"83809","role":"84016","permissions":{"know_of":{"value":true,"source":"84015"},"membership":{"value":false,"source":null},"visible":{"value":true,"source":"84015"},"voiced":{"value":true,"source":"84015"},"edit_entries":{"value":true,"source":"84015"},"edit_thread":{"value":true,"source":"84015"},"edit_thread_description":{"value":true,"source":"84015"},"edit_thread_color":{"value":true,"source":"84015"},"delete_thread":{"value":false,"source":null},"create_subthreads":{"value":false,"source":null},"create_sidebars":{"value":true,"source":"84015"},"join_thread":{"value":false,"source":null},"edit_permissions":{"value":false,"source":null},"add_members":{"value":false,"source":null},"remove_members":{"value":false,"source":null},"change_role":{"value":false,"source":null},"leave_thread":{"value":false,"source":null},"react_to_message":{"value":true,"source":"84015"},"edit_message":{"value":true,"source":"84015"}},"isSender":true},{"id":"83969","role":"84016","permissions":{"know_of":{"value":true,"source":"84015"},"membership":{"value":false,"source":null},"visible":{"value":true,"source":"84015"},"voiced":{"value":true,"source":"84015"},"edit_entries":{"value":true,"source":"84015"},"edit_thread":{"value":true,"source":"84015"},"edit_thread_description":{"value":true,"source":"84015"},"edit_thread_color":{"value":true,"source":"84015"},"delete_thread":{"value":false,"source":null},"create_subthreads":{"value":false,"source":null},"create_sidebars":{"value":true,"source":"84015"},"join_thread":{"value":false,"source":null},"edit_permissions":{"value":false,"source":null},"add_members":{"value":false,"source":null},"remove_members":{"value":false,"source":null},"change_role":{"value":false,"source":null},"leave_thread":{"value":false,"source":null},"react_to_message":{"value":true,"source":"84015"},"edit_message":{"value":true,"source":"84015"}},"isSender":true}]', + '[{"id":"256","role":null,"permissions":{"know_of":{"value":true,"source":"1"},"visible":{"value":true,"source":"1"},"voiced":{"value":true,"source":"1"},"edit_entries":{"value":true,"source":"1"},"edit_thread":{"value":true,"source":"1"},"edit_thread_description":{"value":true,"source":"1"},"edit_thread_color":{"value":true,"source":"1"},"delete_thread":{"value":true,"source":"1"},"create_subthreads":{"value":true,"source":"1"},"create_sidebars":{"value":true,"source":"1"},"join_thread":{"value":true,"source":"1"},"edit_permissions":{"value":true,"source":"1"},"add_members":{"value":true,"source":"1"},"remove_members":{"value":true,"source":"1"},"change_role":{"value":true,"source":"1"},"leave_thread":{"value":false,"source":null},"react_to_message":{"value":false,"source":null},"edit_message":{"value":false,"source":null}},"isSender":false},{"id":"83809","role":"84016","permissions":{"know_of":{"value":true,"source":"84015"},"visible":{"value":true,"source":"84015"},"voiced":{"value":true,"source":"84015"},"edit_entries":{"value":true,"source":"84015"},"edit_thread":{"value":true,"source":"84015"},"edit_thread_description":{"value":true,"source":"84015"},"edit_thread_color":{"value":true,"source":"84015"},"delete_thread":{"value":false,"source":null},"create_subthreads":{"value":false,"source":null},"create_sidebars":{"value":true,"source":"84015"},"join_thread":{"value":false,"source":null},"edit_permissions":{"value":false,"source":null},"add_members":{"value":false,"source":null},"remove_members":{"value":false,"source":null},"change_role":{"value":false,"source":null},"leave_thread":{"value":false,"source":null},"react_to_message":{"value":true,"source":"84015"},"edit_message":{"value":true,"source":"84015"}},"isSender":true},{"id":"83969","role":"84016","permissions":{"know_of":{"value":true,"source":"84015"},"visible":{"value":true,"source":"84015"},"voiced":{"value":true,"source":"84015"},"edit_entries":{"value":true,"source":"84015"},"edit_thread":{"value":true,"source":"84015"},"edit_thread_description":{"value":true,"source":"84015"},"edit_thread_color":{"value":true,"source":"84015"},"delete_thread":{"value":false,"source":null},"create_subthreads":{"value":false,"source":null},"create_sidebars":{"value":true,"source":"84015"},"join_thread":{"value":false,"source":null},"edit_permissions":{"value":false,"source":null},"add_members":{"value":false,"source":null},"remove_members":{"value":false,"source":null},"change_role":{"value":false,"source":null},"leave_thread":{"value":false,"source":null},"react_to_message":{"value":true,"source":"84015"},"edit_message":{"value":true,"source":"84015"}},"isSender":true}]', roles: '{"84016":{"id":"84016","name":"Members","permissions":{"know_of":true,"visible":true,"voiced":true,"react_to_message":true,"edit_message":true,"edit_entries":true,"edit_thread":true,"edit_thread_color":true,"edit_thread_description":true,"create_sidebars":true,"descendant_open_know_of":true,"descendant_open_visible":true,"child_open_join_thread":true},"isDefault":true}}', currentUser: - '{"role":"84016","permissions":{"know_of":{"value":true,"source":"84015"},"membership":{"value":false,"source":null},"visible":{"value":true,"source":"84015"},"voiced":{"value":true,"source":"84015"},"edit_entries":{"value":true,"source":"84015"},"edit_thread":{"value":true,"source":"84015"},"edit_thread_description":{"value":true,"source":"84015"},"edit_thread_color":{"value":true,"source":"84015"},"delete_thread":{"value":false,"source":null},"create_subthreads":{"value":false,"source":null},"create_sidebars":{"value":true,"source":"84015"},"join_thread":{"value":false,"source":null},"edit_permissions":{"value":false,"source":null},"add_members":{"value":false,"source":null},"remove_members":{"value":false,"source":null},"change_role":{"value":false,"source":null},"leave_thread":{"value":false,"source":null},"react_to_message":{"value":true,"source":"84015"},"edit_message":{"value":true,"source":"84015"}},"subscription":{"home":true,"pushNotifs":true},"unread":false}', + '{"role":"84016","permissions":{"know_of":{"value":true,"source":"84015"},"visible":{"value":true,"source":"84015"},"voiced":{"value":true,"source":"84015"},"edit_entries":{"value":true,"source":"84015"},"edit_thread":{"value":true,"source":"84015"},"edit_thread_description":{"value":true,"source":"84015"},"edit_thread_color":{"value":true,"source":"84015"},"delete_thread":{"value":false,"source":null},"create_subthreads":{"value":false,"source":null},"create_sidebars":{"value":true,"source":"84015"},"join_thread":{"value":false,"source":null},"edit_permissions":{"value":false,"source":null},"add_members":{"value":false,"source":null},"remove_members":{"value":false,"source":null},"change_role":{"value":false,"source":null},"leave_thread":{"value":false,"source":null},"react_to_message":{"value":true,"source":"84015"},"edit_message":{"value":true,"source":"84015"}},"subscription":{"home":true,"pushNotifs":true},"unread":false}', repliesCount: 0, containingThreadID: '1', community: '1',