From 3bdaaf103f336f15fa6171c14cd96c30515618b0 Mon Sep 17 00:00:00 2001 From: Emma Casolin Date: Fri, 4 Feb 2022 12:30:24 +1100 Subject: [PATCH] Fixed `identities search` command to search connected identities Unattended background discovery for Discovery domain Adding trusted nodes/identities to the Gestalt Graph --- src/PolykeyAgent.ts | 10 +- src/bin/identities/CommandAuthenticated.ts | 84 ++ src/bin/identities/CommandDiscover.ts | 4 +- src/bin/identities/CommandIdentities.ts | 2 + src/bin/identities/CommandSearch.ts | 96 ++- src/bin/identities/CommandTrust.ts | 23 +- src/bin/utils/parsers.ts | 33 + src/client/GRPCClientClient.ts | 33 +- .../service/gestaltsDiscoveryByIdentity.ts | 10 +- src/client/service/gestaltsDiscoveryByNode.ts | 10 +- .../service/gestaltsGestaltTrustByIdentity.ts | 78 ++ .../service/gestaltsGestaltTrustByNode.ts | 66 ++ .../service/identitiesAuthenticatedGet.ts | 68 ++ .../service/identitiesInfoConnectedGet.ts | 124 +++ src/client/service/identitiesInfoGet.ts | 99 ++- .../service/identitiesInfoGetConnected.ts | 75 -- src/client/service/index.ts | 10 +- src/discovery/Discovery.ts | 516 +++++++----- src/discovery/errors.ts | 11 +- src/discovery/types.ts | 11 + src/discovery/utils.ts | 19 + src/gestalts/GestaltGraph.ts | 19 +- .../providers/github/GitHubProvider.ts | 48 +- src/identities/utils.ts | 40 +- .../js/polykey/v1/client_service_grpc_pb.d.ts | 89 ++- .../js/polykey/v1/client_service_grpc_pb.js | 62 +- .../polykey/v1/identities/identities_pb.d.ts | 93 ++- .../js/polykey/v1/identities/identities_pb.js | 556 ++++++++++--- .../schemas/polykey/v1/client_service.proto | 7 +- .../polykey/v1/identities/identities.proto | 23 +- tests/bin/identities/identities.test.ts | 195 +++-- tests/client/rpcGestalts.test.ts | 37 +- .../gestaltsGestaltTrustByIdentity.test.ts | 470 +++++++++++ .../gestaltsGestaltTrustByNode.test.ts | 352 +++++++++ .../identitiesAuthenticatedGet.test.ts | 236 ++++++ .../identitiesInfoConnectedGet.test.ts | 739 ++++++++++++++++++ .../client/service/identitiesInfoGet.test.ts | 397 +++++++++- .../identitiesInfoGetConnected.test.ts | 161 ---- tests/discovery/Discovery.test.ts | 301 ++++++- tests/gestalts/GestaltGraph.test.ts | 32 +- tests/identities/IdentitiesManager.test.ts | 6 +- tests/identities/TestProvider.ts | 42 +- 42 files changed, 4425 insertions(+), 862 deletions(-) create mode 100644 src/bin/identities/CommandAuthenticated.ts create mode 100644 src/client/service/gestaltsGestaltTrustByIdentity.ts create mode 100644 src/client/service/gestaltsGestaltTrustByNode.ts create mode 100644 src/client/service/identitiesAuthenticatedGet.ts create mode 100644 src/client/service/identitiesInfoConnectedGet.ts delete mode 100644 src/client/service/identitiesInfoGetConnected.ts create mode 100644 src/discovery/types.ts create mode 100644 src/discovery/utils.ts create mode 100644 tests/client/service/gestaltsGestaltTrustByIdentity.test.ts create mode 100644 tests/client/service/gestaltsGestaltTrustByNode.test.ts create mode 100644 tests/client/service/identitiesAuthenticatedGet.test.ts create mode 100644 tests/client/service/identitiesInfoConnectedGet.test.ts delete mode 100644 tests/client/service/identitiesInfoGetConnected.test.ts diff --git a/src/PolykeyAgent.ts b/src/PolykeyAgent.ts index 02c68cb26..7eaafaff2 100644 --- a/src/PolykeyAgent.ts +++ b/src/PolykeyAgent.ts @@ -2,7 +2,6 @@ import type { FileSystem } from './types'; import type { PolykeyWorkerManagerInterface } from './workers/types'; import type { Host, Port } from './network/types'; import type { SeedNodes } from './nodes/types'; - import type { RootKeyPairChangeData } from './keys/types'; import path from 'path'; import process from 'process'; @@ -303,6 +302,7 @@ class PolykeyAgent { discovery = discovery ?? (await Discovery.createDiscovery({ + db, keyManager, gestaltGraph, identitiesManager, @@ -358,7 +358,7 @@ class PolykeyAgent { await sessionManager?.stop(); await notificationsManager?.stop(); await vaultManager?.stop(); - await discovery?.destroy(); + await discovery?.stop(); await revProxy?.stop(); await fwdProxy?.stop(); await gestaltGraph?.stop(); @@ -626,6 +626,7 @@ class PolykeyAgent { await this.nodeConnectionManager.start(); await this.nodeGraph.start({ fresh }); await this.nodeConnectionManager.syncNodeGraph(); + await this.discovery.start({ fresh }); await this.vaultManager.start({ fresh }); await this.notificationsManager.start({ fresh }); await this.sessionManager.start({ fresh }); @@ -644,7 +645,7 @@ class PolykeyAgent { await this.sessionManager?.stop(); await this.notificationsManager?.stop(); await this.vaultManager?.stop(); - await this.discovery?.destroy(); + await this.discovery?.stop(); await this.revProxy?.stop(); await this.fwdProxy?.stop(); await this.grpcServerAgent?.stop(); @@ -671,9 +672,9 @@ class PolykeyAgent { await this.sessionManager.stop(); await this.notificationsManager.stop(); await this.vaultManager.stop(); - await this.discovery.destroy(); await this.nodeConnectionManager.stop(); await this.nodeGraph.stop(); + await this.discovery.stop(); await this.revProxy.stop(); await this.fwdProxy.stop(); await this.grpcServerAgent.stop(); @@ -698,6 +699,7 @@ class PolykeyAgent { await this.notificationsManager.destroy(); await this.vaultManager.destroy(); await this.nodeGraph.destroy(); + await this.discovery.destroy(); await this.gestaltGraph.destroy(); await this.acl.destroy(); await this.sigchain.destroy(); diff --git a/src/bin/identities/CommandAuthenticated.ts b/src/bin/identities/CommandAuthenticated.ts new file mode 100644 index 000000000..a5f9006de --- /dev/null +++ b/src/bin/identities/CommandAuthenticated.ts @@ -0,0 +1,84 @@ +import type PolykeyClient from '../../PolykeyClient'; +import type { IdentityId, ProviderId } from '../../identities/types'; +import CommandPolykey from '../CommandPolykey'; +import * as binOptions from '../utils/options'; +import * as binUtils from '../utils'; +import * as parsers from '../utils/parsers'; +import * as binProcessors from '../utils/processors'; + +class CommandAuthenticated extends CommandPolykey { + constructor(...args: ConstructorParameters) { + super(...args); + this.name('authenticated'); + this.description('Lists all authenticated identities across all providers'); + this.option( + '-pi, --provider-id [providerId]', + 'Digital identity provider to retrieve tokens from', + parsers.parseProviderId, + ); + this.addOption(binOptions.nodeId); + this.addOption(binOptions.clientHost); + this.addOption(binOptions.clientPort); + this.action(async (options) => { + const { default: PolykeyClient } = await import('../../PolykeyClient'); + const identitiesPB = await import( + '../../proto/js/polykey/v1/identities/identities_pb' + ); + const clientOptions = await binProcessors.processClientOptions( + options.nodePath, + options.nodeId, + options.clientHost, + options.clientPort, + this.fs, + this.logger.getChild(binProcessors.processClientOptions.name), + ); + const meta = await binProcessors.processAuthentication( + options.passwordFile, + this.fs, + ); + let pkClient: PolykeyClient; + let genReadable: ReturnType< + typeof pkClient.grpcClient.identitiesAuthenticatedGet + >; + this.exitHandlers.handlers.push(async () => { + if (genReadable != null) genReadable.stream.cancel(); + if (pkClient != null) await pkClient.stop(); + }); + try { + pkClient = await PolykeyClient.createPolykeyClient({ + nodePath: options.nodePath, + nodeId: clientOptions.nodeId, + host: clientOptions.clientHost, + port: clientOptions.clientPort, + logger: this.logger.getChild(PolykeyClient.name), + }); + const optionalProviderMessage = new identitiesPB.OptionalProvider(); + if (options.providerId) { + optionalProviderMessage.setProviderId(options.providerId); + } + await binUtils.retryAuthentication(async (auth) => { + const genReadable = pkClient.grpcClient.identitiesAuthenticatedGet( + optionalProviderMessage, + auth, + ); + for await (const val of genReadable) { + const output = { + providerId: val.getProviderId() as ProviderId, + identityId: val.getIdentityId() as IdentityId, + }; + process.stdout.write( + binUtils.outputFormatter({ + type: options.format === 'json' ? 'json' : 'dict', + data: output, + }), + ); + } + }, meta); + } finally { + if (pkClient! != null) await pkClient.stop(); + } + }); + } +} + +export default CommandAuthenticated; diff --git a/src/bin/identities/CommandDiscover.ts b/src/bin/identities/CommandDiscover.ts index 9d08ca105..c005a2260 100644 --- a/src/bin/identities/CommandDiscover.ts +++ b/src/bin/identities/CommandDiscover.ts @@ -10,9 +10,7 @@ class CommandDiscover extends CommandPolykey { constructor(...args: ConstructorParameters) { super(...args); this.name('discover'); - this.description( - 'Starts Discovery Process using Node or Identity as a Starting Point', - ); + this.description('Adds a Node or Identity to the Discovery Queue'); this.argument( '', 'Node ID or `Provider ID:Identity ID`', diff --git a/src/bin/identities/CommandIdentities.ts b/src/bin/identities/CommandIdentities.ts index 7854513a6..db52099de 100644 --- a/src/bin/identities/CommandIdentities.ts +++ b/src/bin/identities/CommandIdentities.ts @@ -1,5 +1,6 @@ import CommandAllow from './CommandAllow'; import CommandAuthenticate from './CommandAuthenticate'; +import CommandAuthenticated from './CommandAuthenticated'; import CommandClaim from './CommandClaim'; import CommandDisallow from './CommandDisallow'; import CommandDiscover from './CommandDiscover'; @@ -18,6 +19,7 @@ class CommandIdentities extends CommandPolykey { this.description('Identities Operations'); this.addCommand(new CommandAllow(...args)); this.addCommand(new CommandAuthenticate(...args)); + this.addCommand(new CommandAuthenticated(...args)); this.addCommand(new CommandClaim(...args)); this.addCommand(new CommandDisallow(...args)); this.addCommand(new CommandDiscover(...args)); diff --git a/src/bin/identities/CommandSearch.ts b/src/bin/identities/CommandSearch.ts index 03b5a4393..1eea64c2d 100644 --- a/src/bin/identities/CommandSearch.ts +++ b/src/bin/identities/CommandSearch.ts @@ -1,7 +1,9 @@ import type PolykeyClient from '../../PolykeyClient'; +import type { IdentityId, ProviderId } from '../../identities/types'; import CommandPolykey from '../CommandPolykey'; import * as binOptions from '../utils/options'; import * as binUtils from '../utils'; +import * as parsers from '../utils/parsers'; import * as binProcessors from '../utils/processors'; class CommandSearch extends CommandPolykey { @@ -10,13 +12,37 @@ class CommandSearch extends CommandPolykey { this.name('search'); this.description('Searches a Provider for any Connected Identities'); this.argument( - '', - 'Name of the digital identity provider to search on', + '[searchTerms...]', + 'Search parameters to apply to connected identities', + ); + this.option( + '-pi, --provider-id [providerId...]', + 'Digital identity provider(s) to search on', + parsers.parseProviderIdList, + ); + this.option( + '-aii, --auth-identity-id, [authIdentityId]', + 'Name of your own authenticated identity to find connected identities of', + parsers.parseIdentityId, + ); + this.option( + '-ii, --identity-id [identityId]', + 'Name of the digital identity to search for', + parsers.parseIdentityId, + ); + this.option( + '-d, --disconnected', + 'Include disconnected identities in search', + ); + this.option( + '-l, --limit [number]', + 'Limit the number of search results to display to a specific number', + parsers.parseInteger, ); this.addOption(binOptions.nodeId); this.addOption(binOptions.clientHost); this.addOption(binOptions.clientPort); - this.action(async (providerId, options) => { + this.action(async (searchTerms, options) => { const { default: PolykeyClient } = await import('../../PolykeyClient'); const identitiesPB = await import( '../../proto/js/polykey/v1/identities/identities_pb' @@ -34,7 +60,11 @@ class CommandSearch extends CommandPolykey { this.fs, ); let pkClient: PolykeyClient; + let genReadable: ReturnType< + typeof pkClient.grpcClient.identitiesInfoConnectedGet + >; this.exitHandlers.handlers.push(async () => { + if (genReadable != null) genReadable.stream.cancel(); if (pkClient != null) await pkClient.stop(); }); try { @@ -45,25 +75,49 @@ class CommandSearch extends CommandPolykey { port: clientOptions.clientPort, logger: this.logger.getChild(PolykeyClient.name), }); - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(providerId); - const res = await binUtils.retryAuthentication( - (auth) => - pkClient.grpcClient.identitiesInfoGet(providerMessage, auth), - meta, - ); - let output = ''; - if (res.getIdentityId() && res.getProviderId()) { - output = `${res.getProviderId()}:${res.getIdentityId()}`; - } else { - this.logger.info('No Connected Identities found for Provider'); + const providerSearchMessage = new identitiesPB.ProviderSearch(); + providerSearchMessage.setSearchTermList(searchTerms); + if (options.providerId) { + providerSearchMessage.setProviderIdList(options.providerId); + } + if (options.authIdentityId) { + providerSearchMessage.setAuthIdentityId(options.authIdentityId); + } + if (options.disconnected) { + providerSearchMessage.setDisconnected(true); + } + if (options.limit) { + providerSearchMessage.setLimit(options.limit); } - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [output], - }), - ); + await binUtils.retryAuthentication(async (auth) => { + if (options.identity) { + providerSearchMessage.setIdentityId(options.identity); + genReadable = pkClient.grpcClient.identitiesInfoGet( + providerSearchMessage, + auth, + ); + } else { + genReadable = pkClient.grpcClient.identitiesInfoConnectedGet( + providerSearchMessage, + auth, + ); + } + for await (const val of genReadable) { + const output = { + providerId: val.getProvider()!.getProviderId() as ProviderId, + identityId: val.getProvider()!.getIdentityId() as IdentityId, + name: val.getName(), + email: val.getEmail(), + url: val.getUrl(), + }; + process.stdout.write( + binUtils.outputFormatter({ + type: options.format === 'json' ? 'json' : 'dict', + data: output, + }), + ); + } + }, meta); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/bin/identities/CommandTrust.ts b/src/bin/identities/CommandTrust.ts index 284f32de7..0fd432a81 100644 --- a/src/bin/identities/CommandTrust.ts +++ b/src/bin/identities/CommandTrust.ts @@ -24,9 +24,6 @@ class CommandTrust extends CommandPolykey { const identitiesPB = await import( '../../proto/js/polykey/v1/identities/identities_pb' ); - const permissionsPB = await import( - '../../proto/js/polykey/v1/permissions/permissions_pb' - ); const nodesPB = await import('../../proto/js/polykey/v1/nodes/nodes_pb'); const clientOptions = await binProcessors.processClientOptions( options.nodePath, @@ -52,32 +49,24 @@ class CommandTrust extends CommandPolykey { port: clientOptions.clientPort, logger: this.logger.getChild(PolykeyClient.name), }); - const action = 'notify'; - const setActionMessage = new permissionsPB.ActionSet(); - setActionMessage.setAction(action); if (gestaltId.type === 'node') { - // Setting by Node + // Setting by Node. const nodeMessage = new nodesPB.Node(); nodeMessage.setNodeId(gestaltId.nodeId); - setActionMessage.setNode(nodeMessage); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsSetByNode( - setActionMessage, - auth, - ), + pkClient.grpcClient.gestaltsGestaltTrustByNode(nodeMessage, auth), meta, ); } else { // Setting by Identity const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(gestaltId.providerId!); - providerMessage.setIdentityId(gestaltId.identityId!); - setActionMessage.setIdentity(providerMessage); + providerMessage.setProviderId(gestaltId.providerId); + providerMessage.setIdentityId(gestaltId.identityId); await binUtils.retryAuthentication( (auth) => - pkClient.grpcClient.gestaltsActionsSetByIdentity( - setActionMessage, + pkClient.grpcClient.gestaltsGestaltTrustByIdentity( + providerMessage, auth, ), meta, diff --git a/src/bin/utils/parsers.ts b/src/bin/utils/parsers.ts index c871f2596..d1859418f 100644 --- a/src/bin/utils/parsers.ts +++ b/src/bin/utils/parsers.ts @@ -21,6 +21,26 @@ function validateParserToArgParser( }; } +/** + * Converts a validation parser to commander variadic argument parser. + * Variadic options/arguments are always space-separated. + */ +function validateParserToArgListParser( + validate: (data: string) => T, +): (data: string) => Array { + return (data: string) => { + try { + return data.split(' ').map(validate); + } catch (e) { + if (e instanceof validationErrors.ErrorParse) { + throw new commander.InvalidArgumentError(e.message); + } else { + throw e; + } + } + }; +} + const parseInteger = validateParserToArgParser(validationUtils.parseInteger); const parseNumber = validateParserToArgParser(validationUtils.parseNumber); const parseNodeId = validateParserToArgParser(validationUtils.parseNodeId); @@ -37,6 +57,16 @@ const parseNetwork = validateParserToArgParser(validationUtils.parseNetwork); const parseSeedNodes = validateParserToArgParser( validationUtils.parseSeedNodes, ); +const parseProviderId = validateParserToArgParser( + validationUtils.parseProviderId, +); +const parseIdentityId = validateParserToArgParser( + validationUtils.parseIdentityId, +); + +const parseProviderIdList = validateParserToArgListParser( + validationUtils.parseProviderId, +); function parseCoreCount(v: string): number | undefined { if (v === 'all') { @@ -70,6 +100,9 @@ export { parsePort, parseNetwork, parseSeedNodes, + parseProviderId, + parseIdentityId, + parseProviderIdList, parseCoreCount, parseSecretPath, }; diff --git a/src/client/GRPCClientClient.ts b/src/client/GRPCClientClient.ts index e97b3dfeb..ae0de84f1 100644 --- a/src/client/GRPCClientClient.ts +++ b/src/client/GRPCClientClient.ts @@ -19,7 +19,8 @@ import Logger from '@matrixai/logger'; import * as clientErrors from './errors'; import * as clientUtils from './utils'; import { ClientServiceClient } from '../proto/js/polykey/v1/client_service_grpc_pb'; -import { GRPCClient, utils as grpcUtils } from '../grpc'; +import { GRPCClient } from '../grpc'; +import * as grpcUtils from '../grpc/utils'; interface GRPCClientClient extends CreateDestroy {} @CreateDestroy() @@ -489,6 +490,22 @@ class GRPCClientClient extends GRPCClient { )(...args); } + @ready(new clientErrors.ErrorClientClientDestroyed()) + public gestaltsGestaltTrustByNode(...args) { + return grpcUtils.promisifyUnaryCall( + this.client, + this.client.gestaltsGestaltTrustByNode, + )(...args); + } + + @ready(new clientErrors.ErrorClientClientDestroyed()) + public gestaltsGestaltTrustByIdentity(...args) { + return grpcUtils.promisifyUnaryCall( + this.client, + this.client.gestaltsGestaltTrustByIdentity, + )(...args); + } + @ready(new clientErrors.ErrorClientClientDestroyed()) public identitiesTokenPut(...args) { return grpcUtils.promisifyUnaryCall( @@ -562,16 +579,16 @@ class GRPCClientClient extends GRPCClient { } @ready(new clientErrors.ErrorClientClientDestroyed()) - public identitiesInfoGetConnected(...args) { + public identitiesInfoConnectedGet(...args) { return grpcUtils.promisifyReadableStreamCall( this.client, - this.client.identitiesInfoGetConnected, + this.client.identitiesInfoConnectedGet, )(...args); } @ready(new clientErrors.ErrorClientClientDestroyed()) public identitiesInfoGet(...args) { - return grpcUtils.promisifyUnaryCall( + return grpcUtils.promisifyReadableStreamCall( this.client, this.client.identitiesInfoGet, )(...args); @@ -585,6 +602,14 @@ class GRPCClientClient extends GRPCClient { )(...args); } + @ready(new clientErrors.ErrorClientClientDestroyed()) + public identitiesAuthenticatedGet(...args) { + return grpcUtils.promisifyReadableStreamCall( + this.client, + this.client.identitiesAuthenticatedGet, + )(...args); + } + @ready(new clientErrors.ErrorClientClientDestroyed()) public notificationsSend(...args) { return grpcUtils.promisifyUnaryCall( diff --git a/src/client/service/gestaltsDiscoveryByIdentity.ts b/src/client/service/gestaltsDiscoveryByIdentity.ts index 75199dfb7..4ebeae0ce 100644 --- a/src/client/service/gestaltsDiscoveryByIdentity.ts +++ b/src/client/service/gestaltsDiscoveryByIdentity.ts @@ -3,9 +3,10 @@ import type { Authenticate } from '../types'; import type { Discovery } from '../../discovery'; import type { IdentityId, ProviderId } from '../../identities/types'; import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; +import * as grpcUtils from '../../grpc/utils'; +import * as validationUtils from '../../validation/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function gestaltsDiscoveryByIdentity({ @@ -42,10 +43,7 @@ function gestaltsDiscoveryByIdentity({ identityId: call.request.getIdentityId(), }, ); - const gen = discovery.discoverGestaltByIdentity(providerId, identityId); - for await (const _ of gen) { - // Empty - } + await discovery.queueDiscoveryByIdentity(providerId, identityId); callback(null, response); return; } catch (e) { diff --git a/src/client/service/gestaltsDiscoveryByNode.ts b/src/client/service/gestaltsDiscoveryByNode.ts index 8cb8910ae..c8f900141 100644 --- a/src/client/service/gestaltsDiscoveryByNode.ts +++ b/src/client/service/gestaltsDiscoveryByNode.ts @@ -3,9 +3,10 @@ import type { Authenticate } from '../types'; import type { Discovery } from '../../discovery'; import type { NodeId } from '../../nodes/types'; import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; +import * as grpcUtils from '../../grpc/utils'; +import * as validationUtils from '../../validation/utils'; import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; function gestaltsDiscoveryByNode({ @@ -38,10 +39,7 @@ function gestaltsDiscoveryByNode({ nodeId: call.request.getNodeId(), }, ); - const gen = discovery.discoverGestaltByNode(nodeId); - for await (const _ of gen) { - // Empty - } + await discovery.queueDiscoveryByNode(nodeId); callback(null, response); return; } catch (e) { diff --git a/src/client/service/gestaltsGestaltTrustByIdentity.ts b/src/client/service/gestaltsGestaltTrustByIdentity.ts new file mode 100644 index 000000000..754364f79 --- /dev/null +++ b/src/client/service/gestaltsGestaltTrustByIdentity.ts @@ -0,0 +1,78 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { Authenticate } from '../types'; +import type { GestaltGraph } from '../../gestalts'; +import type { IdentityId, ProviderId } from '../../identities/types'; +import type { Discovery } from '../../discovery'; +import type * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as grpcUtils from '../../grpc/utils'; +import * as validationUtils from '../../validation/utils'; +import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; + +function gestaltsGestaltTrustByIdentity({ + authenticate, + gestaltGraph, + discovery, +}: { + authenticate: Authenticate; + gestaltGraph: GestaltGraph; + discovery: Discovery; +}) { + return async ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData, + ): Promise => { + try { + const response = new utilsPB.EmptyMessage(); + const metadata = await authenticate(call.metadata); + call.sendMetadata(metadata); + const { + providerId, + identityId, + }: { + providerId: ProviderId; + identityId: IdentityId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + providerId: call.request.getProviderId(), + identityId: call.request.getIdentityId(), + }, + ); + // Set the identity in the gestalt graph if not already + if ( + (await gestaltGraph.getGestaltByIdentity(providerId, identityId)) == + null + ) { + // Queue the new identity for discovery + // This will only add the identity to the GG if it is connected to a + // node (required to set permissions for it) + await discovery.queueDiscoveryByIdentity(providerId, identityId); + } + // We can currently only set permissions for identities that are + // connected to at least one node. If these conditions are not met, this + // will throw an error. Since discovery can take time, you may need to + // reattempt this command if it fails on the first attempt and you expect + // there to be a linked node for the identity. + await gestaltGraph.setGestaltActionByIdentity( + providerId, + identityId, + 'notify', + ); + callback(null, response); + return; + } catch (e) { + callback(grpcUtils.fromError(e)); + return; + } + }; +} + +export default gestaltsGestaltTrustByIdentity; diff --git a/src/client/service/gestaltsGestaltTrustByNode.ts b/src/client/service/gestaltsGestaltTrustByNode.ts new file mode 100644 index 000000000..b8f03fb87 --- /dev/null +++ b/src/client/service/gestaltsGestaltTrustByNode.ts @@ -0,0 +1,66 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { Authenticate } from '../types'; +import type { GestaltGraph } from '../../gestalts'; +import type { Discovery } from '../../discovery'; +import type { NodeId } from '../../nodes/types'; +import type * as nodesPB from '../../proto/js/polykey/v1/nodes/nodes_pb'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as grpcUtils from '../../grpc/utils'; +import * as validationUtils from '../../validation/utils'; +import * as utilsPB from '../../proto/js/polykey/v1/utils/utils_pb'; +import * as nodesUtils from '../../nodes/utils'; + +function gestaltsGestaltTrustByNode({ + authenticate, + gestaltGraph, + discovery, +}: { + authenticate: Authenticate; + gestaltGraph: GestaltGraph; + discovery: Discovery; +}) { + return async ( + call: grpc.ServerUnaryCall, + callback: grpc.sendUnaryData, + ): Promise => { + try { + const response = new utilsPB.EmptyMessage(); + const metadata = await authenticate(call.metadata); + call.sendMetadata(metadata); + const { + nodeId, + }: { + nodeId: NodeId; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['nodeId'], () => validationUtils.parseNodeId(value)], + () => value, + ); + }, + { + nodeId: call.request.getNodeId(), + }, + ); + // Set the node in the gestalt graph if not already + if ((await gestaltGraph.getGestaltByNode(nodeId)) == null) { + await gestaltGraph.setNode({ + id: nodesUtils.encodeNodeId(nodeId), + chain: {}, + }); + // Queue the new node for discovery + await discovery.queueDiscoveryByNode(nodeId); + } + // Set notify permission + await gestaltGraph.setGestaltActionByNode(nodeId, 'notify'); + callback(null, response); + return; + } catch (e) { + callback(grpcUtils.fromError(e)); + return; + } + }; +} + +export default gestaltsGestaltTrustByNode; diff --git a/src/client/service/identitiesAuthenticatedGet.ts b/src/client/service/identitiesAuthenticatedGet.ts new file mode 100644 index 000000000..1fd4bb9da --- /dev/null +++ b/src/client/service/identitiesAuthenticatedGet.ts @@ -0,0 +1,68 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { Authenticate } from '../types'; +import type { IdentitiesManager } from '../../identities'; +import type { ProviderId } from '../../identities/types'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as grpcUtils from '../../grpc/utils'; +import * as validationUtils from '../../validation/utils'; +import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; + +function identitiesAuthenticatedGet({ + identitiesManager, + authenticate, +}: { + identitiesManager: IdentitiesManager; + authenticate: Authenticate; +}) { + return async ( + call: grpc.ServerWritableStream< + identitiesPB.OptionalProvider, + identitiesPB.Provider + >, + ): Promise => { + const genWritable = grpcUtils.generatorWritable(call); + try { + const metadata = await authenticate(call.metadata); + call.sendMetadata(metadata); + let providerId: ProviderId | undefined; + if (call.request.hasProviderId()) { + providerId = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerId'], () => validationUtils.parseProviderId(value)], + () => value, + ); + }, + { + providerId: call.request.getProviderId(), + }, + ).providerId; + } + const providerIds: Array = + providerId == null + ? (Object.keys(identitiesManager.getProviders()) as Array) + : [providerId]; + for (const providerId of providerIds) { + const provider = identitiesManager.getProvider(providerId); + if (provider == null) { + continue; + } + const identities = await provider.getAuthIdentityIds(); + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(provider.id); + for (const identityId of identities) { + providerMessage.setIdentityId(identityId); + await genWritable.next(providerMessage); + } + } + await genWritable.next(null); + return; + } catch (e) { + await genWritable.throw(e); + return; + } + }; +} + +export default identitiesAuthenticatedGet; diff --git a/src/client/service/identitiesInfoConnectedGet.ts b/src/client/service/identitiesInfoConnectedGet.ts new file mode 100644 index 000000000..8c358c8a1 --- /dev/null +++ b/src/client/service/identitiesInfoConnectedGet.ts @@ -0,0 +1,124 @@ +import type * as grpc from '@grpc/grpc-js'; +import type { Authenticate } from '../types'; +import type { IdentitiesManager } from '../../identities'; +import type { + IdentityData, + IdentityId, + ProviderId, +} from '../../identities/types'; +import { validateSync } from '../../validation'; +import { matchSync } from '../../utils'; +import * as grpcUtils from '../../grpc/utils'; +import * as validationUtils from '../../validation/utils'; +import * as identitiesErrors from '../../identities/errors'; +import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; + +function identitiesInfoConnectedGet({ + identitiesManager, + authenticate, +}: { + identitiesManager: IdentitiesManager; + authenticate: Authenticate; +}) { + return async ( + call: grpc.ServerWritableStream< + identitiesPB.ProviderSearch, + identitiesPB.Info + >, + ): Promise => { + const genWritable = grpcUtils.generatorWritable(call); + try { + const metadata = await authenticate(call.metadata); + call.sendMetadata(metadata); + const { + providerIds, + }: { + providerIds: Array; + } = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['providerIds'], () => value.map(validationUtils.parseProviderId)], + () => value, + ); + }, + { + providerIds: call.request.getProviderIdList(), + }, + ); + let identityId: IdentityId | undefined; + if (call.request.hasAuthIdentityId()) { + identityId = validateSync( + (keyPath, value) => { + return matchSync(keyPath)( + [['identityId'], () => validationUtils.parseIdentityId(value)], + () => value, + ); + }, + { + identityId: call.request.getAuthIdentityId(), + }, + ).identityId; + } + // Process options that were set + if (providerIds.length === 0) { + Object.keys(identitiesManager.getProviders()).forEach((id) => + providerIds.push(id as ProviderId), + ); + } + const getDisconnected = call.request.getDisconnected(); + if (getDisconnected) { + // Can only get connected identities at this stage + throw new identitiesErrors.ErrorProviderUnimplemented(); + } + const identities: Array> = []; + for (const providerId of providerIds) { + // Get provider from id + const provider = identitiesManager.getProvider(providerId); + if (provider === undefined) { + throw new identitiesErrors.ErrorProviderMissing(); + } + // Get our own authenticated identity in order to query, skip provider + // if not authenticated + const authIdentities = await provider.getAuthIdentityIds(); + if (authIdentities.length === 0) { + break; + } + const authIdentityId = + identityId === undefined ? authIdentities[0] : identityId; + identities.push( + provider.getConnectedIdentityDatas( + authIdentityId, + call.request.getSearchTermList(), + ), + ); + } + let limit: number | undefined; + if (call.request.getLimit() !== '') { + limit = parseInt(call.request.getLimit()); + } + let count = 0; + for (const gen of identities) { + for await (const identity of gen) { + if (limit !== undefined && count >= limit) break; + const identityInfoMessage = new identitiesPB.Info(); + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(identity.providerId); + providerMessage.setIdentityId(identity.identityId); + identityInfoMessage.setProvider(providerMessage); + identityInfoMessage.setName(identity.name ?? ''); + identityInfoMessage.setEmail(identity.email ?? ''); + identityInfoMessage.setUrl(identity.url ?? ''); + await genWritable.next(identityInfoMessage); + count++; + } + } + await genWritable.next(null); + return; + } catch (e) { + await genWritable.throw(e); + return; + } + }; +} + +export default identitiesInfoConnectedGet; diff --git a/src/client/service/identitiesInfoGet.ts b/src/client/service/identitiesInfoGet.ts index dcbaac80d..a2210a3f3 100644 --- a/src/client/service/identitiesInfoGet.ts +++ b/src/client/service/identitiesInfoGet.ts @@ -1,15 +1,19 @@ import type * as grpc from '@grpc/grpc-js'; import type { Authenticate } from '../types'; import type { IdentitiesManager } from '../../identities'; -import type { ProviderId } from '../../identities/types'; -import { utils as grpcUtils } from '../../grpc'; -import { validateSync, utils as validationUtils } from '../../validation'; +import type { + IdentityData, + IdentityId, + ProviderId, +} from '../../identities/types'; +import { validateSync } from '../../validation'; import { matchSync } from '../../utils'; +import * as grpcUtils from '../../grpc/utils'; +import * as validationUtils from '../../validation/utils'; +import * as identitiesUtils from '../../identities/utils'; +import * as identitiesErrors from '../../identities/errors'; import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; -/** - * Gets the first identityId of the local keynode. - */ function identitiesInfoGet({ identitiesManager, authenticate, @@ -18,40 +22,93 @@ function identitiesInfoGet({ authenticate: Authenticate; }) { return async ( - call: grpc.ServerUnaryCall, - callback: grpc.sendUnaryData, + call: grpc.ServerWritableStream< + identitiesPB.ProviderSearch, + identitiesPB.Info + >, ): Promise => { + const genWritable = grpcUtils.generatorWritable(call); try { - const response = new identitiesPB.Provider(); const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); const { - providerId, + providerIds, + identityId, }: { - providerId: ProviderId; + providerIds: Array; + identityId: IdentityId; } = validateSync( (keyPath, value) => { return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], + [['providerIds'], () => value.map(validationUtils.parseProviderId)], + [['identityId'], () => validationUtils.parseIdentityId(value)], () => value, ); }, { - providerId: call.request.getProviderId(), + providerIds: call.request.getProviderIdList(), + identityId: call.request.getIdentityId(), }, ); - const provider = identitiesManager.getProvider(providerId); - if (provider !== undefined) { - const identities = await provider.getAuthIdentityIds(); - response.setProviderId(providerId); - if (identities.length !== 0) { - response.setIdentityId(identities[0]); + // Process options that were set + if (providerIds.length === 0) { + Object.keys(identitiesManager.getProviders()).forEach((id) => + providerIds.push(id as ProviderId), + ); + } + const searchTerms = call.request.getSearchTermList(); + const getDisconnected = call.request.getDisconnected(); + if (getDisconnected) { + // Currently this command performs the same way regardless of whether + // this option is set (i.e. always disconnected) + } + const identities: Array = []; + for (const providerId of providerIds) { + // Get provider from id + const provider = identitiesManager.getProvider(providerId); + if (provider === undefined) { + throw new identitiesErrors.ErrorProviderMissing(); + } + // Get our own authenticated identity in order to query, skip provider + // if not authenticated + // It doesn't matter which one we use since `getIdentityData` does not + // require the identity to be connected + const authIdentities = await provider.getAuthIdentityIds(); + if (authIdentities.length === 0) { + break; + } + // Get identity data + identities.push( + await provider.getIdentityData(authIdentities[0], identityId), + ); + } + let limit: number | undefined; + if (call.request.getLimit() !== '') { + limit = parseInt(call.request.getLimit()); + } + if (limit === undefined || limit > identities.length) { + limit = identities.length; + } + for (let i = 0; i < limit; i++) { + const identity = identities[i]; + if (identity !== undefined) { + if (identitiesUtils.matchIdentityData(identity, searchTerms)) { + const identityInfoMessage = new identitiesPB.Info(); + const providerMessage = new identitiesPB.Provider(); + providerMessage.setProviderId(identity.providerId); + providerMessage.setIdentityId(identity.identityId); + identityInfoMessage.setProvider(providerMessage); + identityInfoMessage.setName(identity.name ?? ''); + identityInfoMessage.setEmail(identity.email ?? ''); + identityInfoMessage.setUrl(identity.url ?? ''); + await genWritable.next(identityInfoMessage); + } } } - callback(null, response); + await genWritable.next(null); return; } catch (e) { - callback(grpcUtils.fromError(e)); + await genWritable.throw(e); return; } }; diff --git a/src/client/service/identitiesInfoGetConnected.ts b/src/client/service/identitiesInfoGetConnected.ts deleted file mode 100644 index 22ab3124a..000000000 --- a/src/client/service/identitiesInfoGetConnected.ts +++ /dev/null @@ -1,75 +0,0 @@ -import type * as grpc from '@grpc/grpc-js'; -import type { Authenticate } from '../types'; -import type { IdentitiesManager } from '../../identities'; -import type { IdentityId, ProviderId } from '../../identities/types'; -import { utils as grpcUtils } from '../../grpc'; -import { errors as identitiesErrors } from '../../identities'; -import { validateSync, utils as validationUtils } from '../../validation'; -import { matchSync } from '../../utils'; -import * as identitiesPB from '../../proto/js/polykey/v1/identities/identities_pb'; - -function identitiesInfoGetConnected({ - identitiesManager, - authenticate, -}: { - identitiesManager: IdentitiesManager; - authenticate: Authenticate; -}) { - return async ( - call: grpc.ServerWritableStream< - identitiesPB.ProviderSearch, - identitiesPB.Info - >, - ): Promise => { - const genWritable = grpcUtils.generatorWritable(call); - try { - const metadata = await authenticate(call.metadata); - call.sendMetadata(metadata); - const { - providerId, - identityId, - }: { - providerId: ProviderId; - identityId: IdentityId; - } = validateSync( - (keyPath, value) => { - return matchSync(keyPath)( - [['providerId'], () => validationUtils.parseProviderId(value)], - [['identityId'], () => validationUtils.parseIdentityId(value)], - () => value, - ); - }, - { - providerId: call.request.getProvider()?.getProviderId(), - identityId: call.request.getProvider()?.getIdentityId(), - }, - ); - const provider = identitiesManager.getProvider(providerId); - if (provider == null) { - throw new identitiesErrors.ErrorProviderMissing(); - } - const identities = provider.getConnectedIdentityDatas( - identityId, - call.request.getSearchTermList(), - ); - for await (const identity of identities) { - const identityInfoMessage = new identitiesPB.Info(); - const providerMessage = new identitiesPB.Provider(); - providerMessage.setProviderId(identity.providerId); - providerMessage.setIdentityId(identity.identityId); - identityInfoMessage.setProvider(providerMessage); - identityInfoMessage.setName(identity.name ?? ''); - identityInfoMessage.setEmail(identity.email ?? ''); - identityInfoMessage.setUrl(identity.url ?? ''); - await genWritable.next(identityInfoMessage); - } - await genWritable.next(null); - return; - } catch (e) { - await genWritable.throw(e); - return; - } - }; -} - -export default identitiesInfoGetConnected; diff --git a/src/client/service/index.ts b/src/client/service/index.ts index c272fd0b7..70864a2f3 100644 --- a/src/client/service/index.ts +++ b/src/client/service/index.ts @@ -33,10 +33,13 @@ import gestaltsDiscoveryByNode from './gestaltsDiscoveryByNode'; import gestaltsGestaltGetByIdentity from './gestaltsGestaltGetByIdentity'; import gestaltsGestaltGetByNode from './gestaltsGestaltGetByNode'; import gestaltsGestaltList from './gestaltsGestaltList'; +import gestaltsGestaltTrustByIdentity from './gestaltsGestaltTrustByIdentity'; +import gestaltsGestaltTrustByNode from './gestaltsGestaltTrustByNode'; import identitiesAuthenticate from './identitiesAuthenticate'; +import identitiesAuthenticatedGet from './identitiesAuthenticatedGet'; import identitiesClaim from './identitiesClaim'; import identitiesInfoGet from './identitiesInfoGet'; -import identitiesInfoGetConnected from './identitiesInfoGetConnected'; +import identitiesInfoConnectedGet from './identitiesInfoConnectedGet'; import identitiesProvidersList from './identitiesProvidersList'; import identitiesTokenDelete from './identitiesTokenDelete'; import identitiesTokenGet from './identitiesTokenGet'; @@ -133,10 +136,13 @@ function createService({ gestaltsGestaltGetByIdentity: gestaltsGestaltGetByIdentity(container), gestaltsGestaltGetByNode: gestaltsGestaltGetByNode(container), gestaltsGestaltList: gestaltsGestaltList(container), + gestaltsGestaltTrustByIdentity: gestaltsGestaltTrustByIdentity(container), + gestaltsGestaltTrustByNode: gestaltsGestaltTrustByNode(container), identitiesAuthenticate: identitiesAuthenticate(container), + identitiesAuthenticatedGet: identitiesAuthenticatedGet(container), identitiesClaim: identitiesClaim(container), identitiesInfoGet: identitiesInfoGet(container), - identitiesInfoGetConnected: identitiesInfoGetConnected(container), + identitiesInfoConnectedGet: identitiesInfoConnectedGet(container), identitiesProvidersList: identitiesProvidersList(container), identitiesTokenDelete: identitiesTokenDelete(container), identitiesTokenGet: identitiesTokenGet(container), diff --git a/src/discovery/Discovery.ts b/src/discovery/Discovery.ts index 7c9a76cfb..b63b6a757 100644 --- a/src/discovery/Discovery.ts +++ b/src/discovery/Discovery.ts @@ -1,6 +1,10 @@ +import type { MutexInterface } from 'async-mutex'; +import type { DB, DBLevel } from '@matrixai/db'; +import type { DiscoveryQueueId, DiscoveryQueueIdGenerator } from './types'; import type { NodeId, NodeInfo } from '../nodes/types'; import type { GestaltGraph } from '../gestalts'; import type { GestaltKey } from '../gestalts/types'; +import type { Provider, IdentitiesManager } from '../identities'; import type { IdentityInfo, ProviderId, @@ -11,63 +15,97 @@ import type { import type { NodeManager } from '../nodes'; import type { Sigchain } from '../sigchain'; import type { KeyManager } from '../keys'; -import type { Provider, IdentitiesManager } from '../identities'; import type { ClaimIdEncoded, Claim, ClaimLinkIdentity } from '../claims/types'; - import type { ChainData } from '../sigchain/types'; +import type { ResourceAcquire } from '../utils'; +import { Mutex } from 'async-mutex'; import Logger from '@matrixai/logger'; -import { CreateDestroy, ready } from '@matrixai/async-init/dist/CreateDestroy'; +import { + CreateDestroyStartStop, + ready, + status, +} from '@matrixai/async-init/dist/CreateDestroyStartStop'; +import { IdInternal } from '@matrixai/id'; +import * as idUtils from '@matrixai/id/dist/utils'; +import * as discoveryUtils from './utils'; import * as discoveryErrors from './errors'; +import * as utils from '../utils'; import * as gestaltsUtils from '../gestalts/utils'; import * as claimsUtils from '../claims/utils'; -import { utils as nodesUtils } from '../nodes'; +import * as nodesUtils from '../nodes/utils'; -interface Discovery extends CreateDestroy {} -@CreateDestroy() +interface Discovery extends CreateDestroyStartStop {} +@CreateDestroyStartStop( + new discoveryErrors.ErrorDiscoveryRunning(), + new discoveryErrors.ErrorDiscoveryDestroyed(), +) class Discovery { - protected gestaltGraph: GestaltGraph; - protected identitiesManager: IdentitiesManager; - protected nodeManager: NodeManager; - protected sigchain: Sigchain; - protected keyManager: KeyManager; - protected logger: Logger; - static async createDiscovery({ + db, keyManager, gestaltGraph, identitiesManager, nodeManager, sigchain, logger = new Logger(this.name), + fresh = false, }: { + db: DB; keyManager: KeyManager; gestaltGraph: GestaltGraph; identitiesManager: IdentitiesManager; nodeManager: NodeManager; sigchain: Sigchain; logger?: Logger; + fresh?: boolean; }): Promise { logger.info(`Creating ${this.name}`); const discovery = new Discovery({ + db, keyManager, gestaltGraph, identitiesManager, - logger: logger, nodeManager, sigchain, + logger, }); + await discovery.start({ fresh }); logger.info(`Created ${this.name}`); return discovery; } - constructor({ + protected logger: Logger; + protected db: DB; + protected sigchain: Sigchain; + protected keyManager: KeyManager; + protected gestaltGraph: GestaltGraph; + protected identitiesManager: IdentitiesManager; + protected nodeManager: NodeManager; + protected discoveryDbDomain: string = this.constructor.name; + protected discoveryQueueDbDomain: Array = [ + this.discoveryDbDomain, + 'queue', + ]; + protected discoveryDb: DBLevel; + protected discoveryQueueDb: DBLevel; + protected lock: Mutex = new Mutex(); + protected discoveryQueueIdGenerator: DiscoveryQueueIdGenerator; + protected visitedVertices = new Set(); + protected discoveryQueue: AsyncGenerator; + protected discoveryProcess: Promise; + protected queuePlug: Mutex = new Mutex(); + protected queuePlugRelease: MutexInterface.Releaser | undefined; + + public constructor({ keyManager, + db, gestaltGraph, identitiesManager, nodeManager, sigchain, logger, }: { + db: DB; keyManager: KeyManager; gestaltGraph: GestaltGraph; identitiesManager: IdentitiesManager; @@ -75,6 +113,7 @@ class Discovery { sigchain: Sigchain; logger: Logger; }) { + this.db = db; this.keyManager = keyManager; this.gestaltGraph = gestaltGraph; this.identitiesManager = identitiesManager; @@ -83,197 +122,305 @@ class Discovery { this.logger = logger; } + public async start({ + fresh = false, + }: { + fresh?: boolean; + } = {}): Promise { + this.logger.info(`Starting ${this.constructor.name}`); + const discoveryDb = await this.db.level(this.discoveryDbDomain); + // Queue stores DiscoveryQueueId -> GestaltKey + const discoveryQueueDb = await this.db.level( + this.discoveryQueueDbDomain[1], + discoveryDb, + ); + if (fresh) { + await discoveryDb.clear(); + } + this.discoveryDb = discoveryDb; + this.discoveryQueueDb = discoveryQueueDb; + // Getting latest ID and creating ID generator + let latestId: DiscoveryQueueId | undefined; + const keyStream = this.discoveryQueueDb.createKeyStream({ + limit: 1, + reverse: true, + }); + for await (const o of keyStream) { + latestId = IdInternal.fromBuffer(o); + } + this.discoveryQueueIdGenerator = + discoveryUtils.createDiscoveryQueueIdGenerator(latestId); + this.discoveryQueue = this.setupDiscoveryQueue(); + this.discoveryProcess = this.runDiscoveryQueue(); + this.logger.info(`Started ${this.constructor.name}`); + } + + public async stop(): Promise { + this.logger.info(`Stopping ${this.constructor.name}`); + if (this.queuePlugRelease != null) { + this.queuePlugRelease(); + } + await this.discoveryProcess; + this.logger.info(`Stopped ${this.constructor.name}`); + } + public async destroy() { this.logger.info(`Destroying ${this.constructor.name}`); + const discoveryDb = await this.db.level(this.discoveryDbDomain); + await discoveryDb.clear(); this.logger.info(`Destroyed ${this.constructor.name}`); } - @ready(new discoveryErrors.ErrorDiscoveryDestroyed()) - public discoverGestaltByNode(nodeId: NodeId) { + + public transaction: ResourceAcquire = async () => { + const release = await this.lock.acquire(); + return [async () => release(), this]; + }; + + /** + * Queues a node for discovery. Internally calls `pushKeyToDiscoveryQueue`. + */ + @ready(new discoveryErrors.ErrorDiscoveryNotRunning()) + public async queueDiscoveryByNode(nodeId: NodeId) { const nodeKey = gestaltsUtils.keyFromNode(nodeId); - return this.discoverGestalt(nodeKey); + await this.pushKeyToDiscoveryQueue(nodeKey); } - @ready(new discoveryErrors.ErrorDiscoveryDestroyed()) - public discoverGestaltByIdentity( + /** + * Queues an identity for discovery. Internally calls + * `pushKeyToDiscoveryQueue`. + */ + @ready(new discoveryErrors.ErrorDiscoveryNotRunning()) + public async queueDiscoveryByIdentity( providerId: ProviderId, identityId: IdentityId, - ): AsyncGenerator { + ) { const identityKey = gestaltsUtils.keyFromIdentity(providerId, identityId); - return this.discoverGestalt(identityKey); + await this.pushKeyToDiscoveryQueue(identityKey); } - protected async *discoverGestalt( - gK: GestaltKey, - ): AsyncGenerator { - const vertexQueue = [gK]; - const visitedVertices = new Set(); - + /** + * Generator for the logic of iterating through the Discovery Queue. + */ + public async *setupDiscoveryQueue(): AsyncGenerator { while (true) { - // Get the next vertex discovered to be in the gestalt - const vertex = vertexQueue.shift(); - if (vertex == null) { - break; - } - - const vertexGId = gestaltsUtils.ungestaltKey(vertex); - if (vertexGId.type === 'node') { - // If the next vertex is a node, find its cryptolinks - // const linkInfos = await this.nodeManager.getCryptolinks(nodeInfo.id); - // need some public API to: - // 1. create connection - // 2. get certificate chain (eventually sigchain) - // 3. get the cryptolinks - // If cannot create connection, then throw some kind of exception - // User display = "Cannot crawl gestalt graph. Please try again later" - - // The sigchain data of the vertex (containing all cryptolinks) - let vertexChainData: ChainData = {}; - // If the vertex we've found is our own node, we simply get our own chain - const nodeId = nodesUtils.decodeNodeId(vertexGId.nodeId)!; - if (nodeId.equals(this.keyManager.getNodeId())) { - const vertexChainDataEncoded = await this.sigchain.getChainData(); - // Decode all our claims - no need to verify (on our own sigchain) - let claimIdEncoded: ClaimIdEncoded; - for (claimIdEncoded in vertexChainDataEncoded) { - vertexChainData[claimIdEncoded] = claimsUtils.decodeClaim( - vertexChainDataEncoded[claimIdEncoded], - ); - } - // Otherwise, request the verified chain data from the node - } else { - vertexChainData = await this.nodeManager.requestChainData(nodeId); - } - - // TODO: for now, the chain data is treated as a 'disjoint' set of - // cryptolink claims from a node to another node/identity - // That is, we have no notion of revokations, or multiple claims to the - // same node/identity. Thus, we simply iterate over this chain of - // cryptolinks. - - // Now have the NodeInfo of this vertex - const vertexNodeInfo: NodeInfo = { - id: nodesUtils.encodeNodeId(nodeId), - chain: vertexChainData, - }; - - // Iterate over each of the claims in the chain (already verified) - // TODO: because we're iterating over keys in a record, I don't believe - // that this will iterate in lexicographical order of keys. For now, - // this doesn't matter though (because of the previous comment). - let claimIdEncoded: ClaimIdEncoded; - for (claimIdEncoded in vertexChainData) { - const claim = vertexChainData[claimIdEncoded]; - - // If the claim is to a node - if (claim.payload.data.type === 'node') { - // Get the chain data of the linked node - const linkedVertexNodeId = nodesUtils.decodeNodeId( - claim.payload.data.node2, - )!; - const linkedVertexChainData = - await this.nodeManager.requestChainData(linkedVertexNodeId); - // With this verified chain, we can link - const linkedVertexNodeInfo: NodeInfo = { - id: nodesUtils.encodeNodeId(linkedVertexNodeId), - chain: linkedVertexChainData, + if (!(await this.queueIsEmpty())) { + for await (const o of this.discoveryQueueDb.createReadStream()) { + const vertexId = IdInternal.fromBuffer(o.key) as DiscoveryQueueId; + const data = o.value as Buffer; + const vertex = await this.db.deserializeDecrypt( + data, + false, + ); + const vertexGId = gestaltsUtils.ungestaltKey(vertex); + if (vertexGId.type === 'node') { + // The sigchain data of the vertex (containing all cryptolinks) + let vertexChainData: ChainData = {}; + // If the vertex we've found is our own node, we simply get our own chain + const nodeId = nodesUtils.decodeNodeId(vertexGId.nodeId)!; + if (nodeId.equals(this.keyManager.getNodeId())) { + const vertexChainDataEncoded = await this.sigchain.getChainData(); + // Decode all our claims - no need to verify (on our own sigchain) + for (const c in vertexChainDataEncoded) { + const claimId = c as ClaimIdEncoded; + vertexChainData[claimId] = claimsUtils.decodeClaim( + vertexChainDataEncoded[claimId], + ); + } + // Otherwise, request the verified chain data from the node + } else { + vertexChainData = await this.nodeManager.requestChainData(nodeId); + } + // TODO: for now, the chain data is treated as a 'disjoint' set of + // cryptolink claims from a node to another node/identity + // That is, we have no notion of revocations, or multiple claims to the + // same node/identity. Thus, we simply iterate over this chain of + // cryptolinks. + // Now have the NodeInfo of this vertex + const vertexNodeInfo: NodeInfo = { + id: nodesUtils.encodeNodeId(nodeId), + chain: vertexChainData, }; - await this.gestaltGraph.linkNodeAndNode( - vertexNodeInfo, - linkedVertexNodeInfo, - ); - - // Add this vertex to the queue if it hasn't already been visited - const linkedVertexGK = - gestaltsUtils.keyFromNode(linkedVertexNodeId); - if (!visitedVertices.has(linkedVertexGK)) { - vertexQueue.push(linkedVertexGK); + // Iterate over each of the claims in the chain (already verified) + // TODO: because we're iterating over keys in a record, I don't believe + // that this will iterate in lexicographical order of keys. For now, + // this doesn't matter though (because of the previous comment). + for (const claimId in vertexChainData) { + const claim: Claim = vertexChainData[claimId as ClaimIdEncoded]; + // If the claim is to a node + if (claim.payload.data.type === 'node') { + // Get the chain data of the linked node + // Could be node1 or node2 in the claim so get the one that's + // not equal to nodeId from above + const node1Id = nodesUtils.decodeNodeId( + claim.payload.data.node1, + )!; + const node2Id = nodesUtils.decodeNodeId( + claim.payload.data.node2, + )!; + const linkedVertexNodeId = node1Id.equals(nodeId) + ? node2Id + : node1Id; + const linkedVertexChainData = + await this.nodeManager.requestChainData(linkedVertexNodeId); + // With this verified chain, we can link + const linkedVertexNodeInfo: NodeInfo = { + id: nodesUtils.encodeNodeId(linkedVertexNodeId), + chain: linkedVertexChainData, + }; + await this.gestaltGraph.linkNodeAndNode( + vertexNodeInfo, + linkedVertexNodeInfo, + ); + // Add this vertex to the queue if it hasn't already been visited + const linkedVertexGK = + gestaltsUtils.keyFromNode(linkedVertexNodeId); + if (!this.visitedVertices.has(linkedVertexGK)) { + await this.pushKeyToDiscoveryQueue(linkedVertexGK); + } + } + // Else the claim is to an identity + if (claim.payload.data.type === 'identity') { + // Attempt to get the identity info on the identity provider + const identityInfo = await this.getIdentityInfo( + claim.payload.data.provider, + claim.payload.data.identity, + ); + // If we can't get identity info, simply skip this claim + if (identityInfo == null) { + continue; + } + // Link the node to the found identity info + await this.gestaltGraph.linkNodeAndIdentity( + vertexNodeInfo, + identityInfo, + ); + // Add this identity vertex to the queue if it is not present + const linkedIdentityGK = gestaltsUtils.keyFromIdentity( + claim.payload.data.provider, + claim.payload.data.identity, + ); + if (!this.visitedVertices.has(linkedIdentityGK)) { + await this.pushKeyToDiscoveryQueue(linkedIdentityGK); + } + } } - } - - // If the claim is to an identity - if (claim.payload.data.type === 'identity') { - // Attempt to get the identity info on the identity provider - const identityInfo = await this.getIdentityInfo( - claim.payload.data.provider, - claim.payload.data.identity, - claim.payload.data.identity, + } else if (vertexGId.type === 'identity') { + // If the next vertex is an identity, perform a social discovery + // Firstly get the identity info of this identity + const vertexIdentityInfo = await this.getIdentityInfo( + vertexGId.providerId, + vertexGId.identityId, ); - // If we can't get identity info, simply skip this claim - if (identityInfo == null) { + // If we don't have identity info, simply skip this vertex + if (vertexIdentityInfo == null) { continue; } - // Link the node to the found identity info - await this.gestaltGraph.linkNodeAndIdentity( - vertexNodeInfo, - identityInfo, - ); - - // Add this identity vertex to the queue if it hasn't already been visited - const linkedIdentityGK = gestaltsUtils.keyFromIdentity( - claim.payload.data.provider, - claim.payload.data.identity, - ); - if (!visitedVertices.has(linkedIdentityGK)) { - vertexQueue.push(linkedIdentityGK); + // Link the identity with each node from its claims on the provider + // Iterate over each of the claims + for (const id in vertexIdentityInfo.claims) { + const identityClaimId = id as IdentityClaimId; + const claim = vertexIdentityInfo.claims[identityClaimId]; + // Claims on an identity provider will always be node -> identity + // So just cast payload data as such + const data = claim.payload.data as ClaimLinkIdentity; + const linkedVertexNodeId = nodesUtils.decodeNodeId(data.node)!; + // Get the chain data of this claimed node (so that we can link in GG) + const linkedVertexChainData = + await this.nodeManager.requestChainData(linkedVertexNodeId); + // With this verified chain, we can link + const linkedVertexNodeInfo: NodeInfo = { + id: nodesUtils.encodeNodeId(linkedVertexNodeId), + chain: linkedVertexChainData, + }; + await this.gestaltGraph.linkNodeAndIdentity( + linkedVertexNodeInfo, + vertexIdentityInfo, + ); + // Add this vertex to the queue if it is not present + const linkedVertexGK = + gestaltsUtils.keyFromNode(linkedVertexNodeId); + if (!this.visitedVertices.has(linkedVertexGK)) { + await this.pushKeyToDiscoveryQueue(linkedVertexGK); + } } } + this.visitedVertices.add(vertex); + await this.removeKeyFromDiscoveryQueue(vertexId); + yield; } - // Add this node vertex to the visited - visitedVertices.add(gestaltsUtils.keyFromNode(nodeId)); - } else if (vertexGId.type === 'identity') { - // If the next vertex is an identity, perform a social discovery - // Firstly get the identity info of this identity - const vertexIdentityInfo = await this.getIdentityInfo( - vertexGId.providerId, - vertexGId.identityId, - vertexGId.identityId, - ); - // If we don't have identity info, simply skip this vertex - if (vertexIdentityInfo == null) { - continue; + } else { + if (!(this[status] === 'stopping')) { + this.queuePlugRelease = await this.queuePlug.acquire(); } + await this.queuePlug.waitForUnlock(); + } + if (this[status] === 'stopping') { + break; + } + } + } - // Link the identity with each node from its claims on the provider - // Iterate over each of the claims - for (const id in vertexIdentityInfo.claims) { - const identityClaimId = id as IdentityClaimId; - const claim = vertexIdentityInfo.claims[identityClaimId]; - // Claims on an identity provider will always be node -> identity - // So just cast payload data as such - const data = claim.payload.data as ClaimLinkIdentity; - const linkedVertexNodeId = nodesUtils.decodeNodeId(data.node)!; - // Get the chain data of this claimed node (so that we can link in GG) - const linkedVertexChainData = await this.nodeManager.requestChainData( - linkedVertexNodeId, - ); - // With this verified chain, we can link - const linkedVertexNodeInfo: NodeInfo = { - id: nodesUtils.encodeNodeId(linkedVertexNodeId), - chain: linkedVertexChainData, - }; - await this.gestaltGraph.linkNodeAndIdentity( - linkedVertexNodeInfo, - vertexIdentityInfo, - ); + /** + * Used for iterating over the discovery queue. This method should run + * continuously whenever the Discovery module is started and should be exited + * only during stopping. + */ + protected async runDiscoveryQueue() { + for await (const _ of this.discoveryQueue) { + // Empty + } + } - // Add this vertex to the queue if it hasn't already been visited - const linkedVertexGK = gestaltsUtils.keyFromNode(linkedVertexNodeId); - if (!visitedVertices.has(linkedVertexGK)) { - vertexQueue.push(linkedVertexGK); - } - } - // Add this identity vertex to the visited - visitedVertices.add( - gestaltsUtils.keyFromIdentity( - vertexGId.providerId, - vertexGId.identityId, - ), - ); + /** + * Simple check for whether the Discovery Queue is empty. Uses a + * transaction lock to ensure consistency. + */ + protected async queueIsEmpty(): Promise { + return await utils.withF([this.transaction], async () => { + let nextDiscoveryQueueId: DiscoveryQueueId | undefined; + const keyStream = this.discoveryQueueDb.createKeyStream({ + limit: 1, + }); + for await (const o of keyStream) { + nextDiscoveryQueueId = IdInternal.fromBuffer(o); } - yield; + if (nextDiscoveryQueueId == null) { + return true; + } + return false; + }); + } + + /** + * Push a Gestalt Key to the Discovery Queue. This process also unlocks + * the queue if it was previously locked (due to being empty). + */ + protected async pushKeyToDiscoveryQueue(gk: GestaltKey) { + await utils.withF([this.transaction], async () => { + const discoveryQueueId = this.discoveryQueueIdGenerator(); + await this.db.put( + this.discoveryQueueDbDomain, + idUtils.toBuffer(discoveryQueueId), + gk, + ); + }); + if (this.queuePlugRelease != null) { + this.queuePlugRelease(); + this.queuePlugRelease = undefined; } } + /** + * Remove a Gestalt Key from the Discovery Queue by its QueueId. This should + * only be done after a Key has been discovered in order to remove it from + * the beginning of the queue. + */ + protected async removeKeyFromDiscoveryQueue(keyId: DiscoveryQueueId) { + await utils.withF([this.transaction], async () => { + await this.db.del(this.discoveryQueueDbDomain, idUtils.toBuffer(keyId)); + }); + } + /** * Helper function to retrieve the IdentityInfo of an identity on a provider. * All claims in the returned IdentityInfo are verified by the node it claims @@ -284,15 +431,24 @@ class Discovery { protected async getIdentityInfo( providerId: ProviderId, identityId: IdentityId, - authIdentityId: IdentityId, ): Promise { const provider = this.identitiesManager.getProvider(providerId); // If we don't have this provider, no identity info to find if (provider == null) { return undefined; } + // Get our own auth identity id + const authIdentityIds = await provider.getAuthIdentityIds(); + // If we don't have one then we can't request data so just skip + if (authIdentityIds === [] || authIdentityIds[0] == null) { + return undefined; + } + const authIdentityId = authIdentityIds[0]; // Get the identity data - const identityData = await provider.getIdentityData(identityId, identityId); + const identityData = await provider.getIdentityData( + authIdentityId, + identityId, + ); // If we don't have identity data, no identity info to find if (identityData == null) { return undefined; diff --git a/src/discovery/errors.ts b/src/discovery/errors.ts index ee359227c..cadf81048 100644 --- a/src/discovery/errors.ts +++ b/src/discovery/errors.ts @@ -2,6 +2,15 @@ import { ErrorPolykey } from '../errors'; class ErrorDiscovery extends ErrorPolykey {} +class ErrorDiscoveryRunning extends ErrorDiscovery {} + class ErrorDiscoveryDestroyed extends ErrorDiscovery {} -export { ErrorDiscovery, ErrorDiscoveryDestroyed }; +class ErrorDiscoveryNotRunning extends ErrorDiscovery {} + +export { + ErrorDiscovery, + ErrorDiscoveryDestroyed, + ErrorDiscoveryRunning, + ErrorDiscoveryNotRunning, +}; diff --git a/src/discovery/types.ts b/src/discovery/types.ts new file mode 100644 index 000000000..9c32ed947 --- /dev/null +++ b/src/discovery/types.ts @@ -0,0 +1,11 @@ +import type { Opaque } from '../types'; +import type { Id } from '../GenericIdTypes'; + +/** + * Used to preserve order in the Discovery Queue. + */ +type DiscoveryQueueId = Opaque<'DiscoveryQueueId', Id>; + +type DiscoveryQueueIdGenerator = () => DiscoveryQueueId; + +export type { DiscoveryQueueId, DiscoveryQueueIdGenerator }; diff --git a/src/discovery/utils.ts b/src/discovery/utils.ts new file mode 100644 index 000000000..b8a9f9808 --- /dev/null +++ b/src/discovery/utils.ts @@ -0,0 +1,19 @@ +import type { DiscoveryQueueId, DiscoveryQueueIdGenerator } from './types'; +import { IdSortable } from '@matrixai/id'; +import { makeId } from '../GenericIdTypes'; + +function makeDiscoveryQueueId(arg: any) { + return makeId(arg); +} + +function createDiscoveryQueueIdGenerator( + lastId?: DiscoveryQueueId, +): DiscoveryQueueIdGenerator { + const idSortableGenerator = new IdSortable({ + lastId, + }); + return (): DiscoveryQueueId => + makeDiscoveryQueueId(idSortableGenerator.get()); +} + +export { makeDiscoveryQueueId, createDiscoveryQueueIdGenerator }; diff --git a/src/gestalts/GestaltGraph.ts b/src/gestalts/GestaltGraph.ts index e9ff881be..4e9036d7a 100644 --- a/src/gestalts/GestaltGraph.ts +++ b/src/gestalts/GestaltGraph.ts @@ -1,4 +1,4 @@ -import type { Buffer } from 'buffer'; +import type { DB, DBLevel, DBOp } from '@matrixai/db'; import type { Gestalt, GestaltAction, @@ -10,10 +10,8 @@ import type { } from './types'; import type { NodeId, NodeInfo } from '../nodes/types'; import type { IdentityId, IdentityInfo, ProviderId } from '../identities/types'; -import type { Permission } from '../acl/types'; -import type { DB, DBLevel, DBOp } from '@matrixai/db'; import type { ACL } from '../acl'; - +import type { Permission } from '../acl/types'; import { Mutex } from 'async-mutex'; import Logger from '@matrixai/logger'; import { @@ -22,9 +20,9 @@ import { } from '@matrixai/async-init/dist/CreateDestroyStartStop'; import * as gestaltsUtils from './utils'; import * as gestaltsErrors from './errors'; -import { utils as aclUtils } from '../acl'; +import * as aclUtils from '../acl/utils'; import * as utils from '../utils'; -import { utils as nodesUtils } from '../nodes'; +import * as nodesUtils from '../nodes/utils'; interface GestaltGraph extends CreateDestroyStartStop {} @CreateDestroyStartStop( @@ -156,12 +154,13 @@ class GestaltGraph { } const gestalts: Array = []; let gestalt: Gestalt; - for (const [gK, gKs] of unvisited) { + for (const gKSet of unvisited) { gestalt = { matrix: {}, nodes: {}, identities: {}, }; + const gK = gKSet[0]; const queue = [gK]; while (true) { const vertex = queue.shift(); @@ -170,7 +169,11 @@ class GestaltGraph { break; } const gId = gestaltsUtils.ungestaltKey(vertex); - const vertexKeys = gKs; + const vertexKeys = unvisited.get(vertex); + if (vertexKeys == null) { + // This should not happen + break; + } gestalt.matrix[vertex] = vertexKeys; if (gId.type === 'node') { const nodeInfo = await this.db.get( diff --git a/src/identities/providers/github/GitHubProvider.ts b/src/identities/providers/github/GitHubProvider.ts index 39f199f32..4dc939999 100644 --- a/src/identities/providers/github/GitHubProvider.ts +++ b/src/identities/providers/github/GitHubProvider.ts @@ -3,18 +3,18 @@ import type { ProviderId, TokenData, IdentityData, + IdentityClaim, + IdentityClaimId, ProviderAuthenticateRequest, } from '../../types'; import type { Claim } from '../../../claims/types'; -import type { IdentityClaim, IdentityClaimId } from '../../../identities/types'; - import { fetch, Request, Headers } from 'cross-fetch'; -import { Searcher } from 'fast-fuzzy'; import cheerio from 'cheerio'; import Logger from '@matrixai/logger'; -import { sleep } from '../../../utils'; import Provider from '../../Provider'; import * as identitiesErrors from '../../errors'; +import * as identitiesUtils from '../../utils'; +import { sleep } from '../../../utils'; class GitHubProvider extends Provider { public readonly id = 'github.com' as ProviderId; @@ -304,7 +304,10 @@ class GitHubProvider extends Provider { authIdentityId, item.login, ); - if (identityData && this.matchIdentityData(identityData, searchTerms)) { + if ( + identityData && + identitiesUtils.matchIdentityData(identityData, searchTerms) + ) { yield identityData; } } @@ -347,7 +350,10 @@ class GitHubProvider extends Provider { authIdentityId, item.login, ); - if (identityData && this.matchIdentityData(identityData, searchTerms)) { + if ( + identityData && + identitiesUtils.matchIdentityData(identityData, searchTerms) + ) { yield identityData; } } @@ -533,36 +539,6 @@ class GitHubProvider extends Provider { }) as Request; } - protected matchIdentityData( - identityData: IdentityData, - searchTerms: Array, - ): boolean { - if (searchTerms.length < 1) { - return true; - } - const searcher = new Searcher([identityData], { - keySelector: (obj) => [ - obj.identityId, - obj.name || '', - obj.email || '', - obj.url || '', - ], - threshold: 0.8, - }); - let matched = false; - for (const searchTerm of searchTerms) { - if (searcher.search(searchTerm).length > 0) { - matched = true; - break; - } - } - if (matched) { - return true; - } else { - return false; - } - } - protected extractClaimIds(html: string): Array { const claimIds: Array = []; const $ = cheerio.load(html); diff --git a/src/identities/utils.ts b/src/identities/utils.ts index 9bb59c441..cd32d7fa4 100644 --- a/src/identities/utils.ts +++ b/src/identities/utils.ts @@ -1,6 +1,8 @@ +import type { IdentityData } from './types'; import os from 'os'; import process from 'process'; import spawn from 'cross-spawn'; +import { Searcher } from 'fast-fuzzy'; function browser(url: string): void { let platform = process.platform; @@ -47,4 +49,40 @@ function browser(url: string): void { browserProcess.unref(); } -export { browser }; +/** + * Check whether a given identity matches at least one search term from a list. + * Use this to filter a list of identiy datas e.g. filtering connected + * identities or a list of identity lookups. + * If the provider has a native search ability, use that instead of this. + */ +function matchIdentityData( + identityData: IdentityData, + searchTerms: Array, +): boolean { + if (searchTerms.length < 1) { + return true; + } + const searcher = new Searcher([identityData], { + keySelector: (obj) => [ + obj.identityId, + obj.name || '', + obj.email || '', + obj.url || '', + ], + threshold: 0.8, + }); + let matched = false; + for (const searchTerm of searchTerms) { + if (searcher.search(searchTerm).length > 0) { + matched = true; + break; + } + } + if (matched) { + return true; + } else { + return false; + } +} + +export { browser, matchIdentityData }; diff --git a/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts b/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts index dc52efc66..d558b7140 100644 --- a/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts +++ b/src/proto/js/polykey/v1/client_service_grpc_pb.d.ts @@ -59,16 +59,19 @@ interface IClientServiceService extends grpc.ServiceDefinition; responseDeserialize: grpc.deserialize; } +interface IClientServiceService_IIdentitiesAuthenticatedGet extends grpc.MethodDefinition { + path: "/polykey.v1.ClientService/IdentitiesAuthenticatedGet"; + requestStream: false; + responseStream: true; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} interface IClientServiceService_IIdentitiesTokenPut extends grpc.MethodDefinition { path: "/polykey.v1.ClientService/IdentitiesTokenPut"; requestStream: false; @@ -478,17 +490,17 @@ interface IClientServiceService_IIdentitiesProvidersList extends grpc.MethodDefi responseSerialize: grpc.serialize; responseDeserialize: grpc.deserialize; } -interface IClientServiceService_IIdentitiesInfoGet extends grpc.MethodDefinition { +interface IClientServiceService_IIdentitiesInfoGet extends grpc.MethodDefinition { path: "/polykey.v1.ClientService/IdentitiesInfoGet"; requestStream: false; - responseStream: false; - requestSerialize: grpc.serialize; - requestDeserialize: grpc.deserialize; - responseSerialize: grpc.serialize; - responseDeserialize: grpc.deserialize; + responseStream: true; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; } -interface IClientServiceService_IIdentitiesInfoGetConnected extends grpc.MethodDefinition { - path: "/polykey.v1.ClientService/IdentitiesInfoGetConnected"; +interface IClientServiceService_IIdentitiesInfoConnectedGet extends grpc.MethodDefinition { + path: "/polykey.v1.ClientService/IdentitiesInfoConnectedGet"; requestStream: false; responseStream: true; requestSerialize: grpc.serialize; @@ -532,6 +544,24 @@ interface IClientServiceService_IGestaltsGestaltGetByIdentity extends grpc.Metho responseSerialize: grpc.serialize; responseDeserialize: grpc.deserialize; } +interface IClientServiceService_IGestaltsGestaltTrustByNode extends grpc.MethodDefinition { + path: "/polykey.v1.ClientService/GestaltsGestaltTrustByNode"; + requestStream: false; + responseStream: false; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} +interface IClientServiceService_IGestaltsGestaltTrustByIdentity extends grpc.MethodDefinition { + path: "/polykey.v1.ClientService/GestaltsGestaltTrustByIdentity"; + requestStream: false; + responseStream: false; + requestSerialize: grpc.serialize; + requestDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; +} interface IClientServiceService_IGestaltsDiscoveryByNode extends grpc.MethodDefinition { path: "/polykey.v1.ClientService/GestaltsDiscoveryByNode"; requestStream: false; @@ -675,16 +705,19 @@ export interface IClientServiceServer extends grpc.UntypedServiceImplementation vaultsVersion: grpc.handleUnaryCall; vaultsLog: grpc.handleServerStreamingCall; identitiesAuthenticate: grpc.handleServerStreamingCall; + identitiesAuthenticatedGet: grpc.handleServerStreamingCall; identitiesTokenPut: grpc.handleUnaryCall; identitiesTokenGet: grpc.handleUnaryCall; identitiesTokenDelete: grpc.handleUnaryCall; identitiesProvidersList: grpc.handleUnaryCall; - identitiesInfoGet: grpc.handleUnaryCall; - identitiesInfoGetConnected: grpc.handleServerStreamingCall; + identitiesInfoGet: grpc.handleServerStreamingCall; + identitiesInfoConnectedGet: grpc.handleServerStreamingCall; identitiesClaim: grpc.handleUnaryCall; gestaltsGestaltList: grpc.handleServerStreamingCall; gestaltsGestaltGetByNode: grpc.handleUnaryCall; gestaltsGestaltGetByIdentity: grpc.handleUnaryCall; + gestaltsGestaltTrustByNode: grpc.handleUnaryCall; + gestaltsGestaltTrustByIdentity: grpc.handleUnaryCall; gestaltsDiscoveryByNode: grpc.handleUnaryCall; gestaltsDiscoveryByIdentity: grpc.handleUnaryCall; gestaltsActionsGetByNode: grpc.handleUnaryCall; @@ -812,6 +845,8 @@ export interface IClientServiceClient { vaultsLog(request: polykey_v1_vaults_vaults_pb.Log, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; identitiesAuthenticate(request: polykey_v1_identities_identities_pb.Provider, options?: Partial): grpc.ClientReadableStream; identitiesAuthenticate(request: polykey_v1_identities_identities_pb.Provider, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; + identitiesAuthenticatedGet(request: polykey_v1_identities_identities_pb.OptionalProvider, options?: Partial): grpc.ClientReadableStream; + identitiesAuthenticatedGet(request: polykey_v1_identities_identities_pb.OptionalProvider, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; identitiesTokenPut(request: polykey_v1_identities_identities_pb.TokenSpecific, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; identitiesTokenPut(request: polykey_v1_identities_identities_pb.TokenSpecific, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; identitiesTokenPut(request: polykey_v1_identities_identities_pb.TokenSpecific, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; @@ -824,11 +859,10 @@ export interface IClientServiceClient { identitiesProvidersList(request: polykey_v1_utils_utils_pb.EmptyMessage, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; identitiesProvidersList(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; identitiesProvidersList(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; - identitiesInfoGet(request: polykey_v1_identities_identities_pb.Provider, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; - identitiesInfoGet(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; - identitiesInfoGet(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; - identitiesInfoGetConnected(request: polykey_v1_identities_identities_pb.ProviderSearch, options?: Partial): grpc.ClientReadableStream; - identitiesInfoGetConnected(request: polykey_v1_identities_identities_pb.ProviderSearch, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; + identitiesInfoGet(request: polykey_v1_identities_identities_pb.ProviderSearch, options?: Partial): grpc.ClientReadableStream; + identitiesInfoGet(request: polykey_v1_identities_identities_pb.ProviderSearch, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; + identitiesInfoConnectedGet(request: polykey_v1_identities_identities_pb.ProviderSearch, options?: Partial): grpc.ClientReadableStream; + identitiesInfoConnectedGet(request: polykey_v1_identities_identities_pb.ProviderSearch, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; identitiesClaim(request: polykey_v1_identities_identities_pb.Provider, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Claim) => void): grpc.ClientUnaryCall; identitiesClaim(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Claim) => void): grpc.ClientUnaryCall; identitiesClaim(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Claim) => void): grpc.ClientUnaryCall; @@ -840,6 +874,12 @@ export interface IClientServiceClient { gestaltsGestaltGetByIdentity(request: polykey_v1_identities_identities_pb.Provider, callback: (error: grpc.ServiceError | null, response: polykey_v1_gestalts_gestalts_pb.Graph) => void): grpc.ClientUnaryCall; gestaltsGestaltGetByIdentity(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_gestalts_gestalts_pb.Graph) => void): grpc.ClientUnaryCall; gestaltsGestaltGetByIdentity(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_gestalts_gestalts_pb.Graph) => void): grpc.ClientUnaryCall; + gestaltsGestaltTrustByNode(request: polykey_v1_nodes_nodes_pb.Node, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + gestaltsGestaltTrustByNode(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + gestaltsGestaltTrustByNode(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + gestaltsGestaltTrustByIdentity(request: polykey_v1_identities_identities_pb.Provider, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + gestaltsGestaltTrustByIdentity(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + gestaltsGestaltTrustByIdentity(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; gestaltsDiscoveryByNode(request: polykey_v1_nodes_nodes_pb.Node, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; gestaltsDiscoveryByNode(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; gestaltsDiscoveryByNode(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; @@ -990,6 +1030,8 @@ export class ClientServiceClient extends grpc.Client implements IClientServiceCl public vaultsLog(request: polykey_v1_vaults_vaults_pb.Log, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; public identitiesAuthenticate(request: polykey_v1_identities_identities_pb.Provider, options?: Partial): grpc.ClientReadableStream; public identitiesAuthenticate(request: polykey_v1_identities_identities_pb.Provider, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; + public identitiesAuthenticatedGet(request: polykey_v1_identities_identities_pb.OptionalProvider, options?: Partial): grpc.ClientReadableStream; + public identitiesAuthenticatedGet(request: polykey_v1_identities_identities_pb.OptionalProvider, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; public identitiesTokenPut(request: polykey_v1_identities_identities_pb.TokenSpecific, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public identitiesTokenPut(request: polykey_v1_identities_identities_pb.TokenSpecific, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public identitiesTokenPut(request: polykey_v1_identities_identities_pb.TokenSpecific, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; @@ -1002,11 +1044,10 @@ export class ClientServiceClient extends grpc.Client implements IClientServiceCl public identitiesProvidersList(request: polykey_v1_utils_utils_pb.EmptyMessage, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; public identitiesProvidersList(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; public identitiesProvidersList(request: polykey_v1_utils_utils_pb.EmptyMessage, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; - public identitiesInfoGet(request: polykey_v1_identities_identities_pb.Provider, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; - public identitiesInfoGet(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; - public identitiesInfoGet(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Provider) => void): grpc.ClientUnaryCall; - public identitiesInfoGetConnected(request: polykey_v1_identities_identities_pb.ProviderSearch, options?: Partial): grpc.ClientReadableStream; - public identitiesInfoGetConnected(request: polykey_v1_identities_identities_pb.ProviderSearch, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; + public identitiesInfoGet(request: polykey_v1_identities_identities_pb.ProviderSearch, options?: Partial): grpc.ClientReadableStream; + public identitiesInfoGet(request: polykey_v1_identities_identities_pb.ProviderSearch, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; + public identitiesInfoConnectedGet(request: polykey_v1_identities_identities_pb.ProviderSearch, options?: Partial): grpc.ClientReadableStream; + public identitiesInfoConnectedGet(request: polykey_v1_identities_identities_pb.ProviderSearch, metadata?: grpc.Metadata, options?: Partial): grpc.ClientReadableStream; public identitiesClaim(request: polykey_v1_identities_identities_pb.Provider, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Claim) => void): grpc.ClientUnaryCall; public identitiesClaim(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Claim) => void): grpc.ClientUnaryCall; public identitiesClaim(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_identities_identities_pb.Claim) => void): grpc.ClientUnaryCall; @@ -1018,6 +1059,12 @@ export class ClientServiceClient extends grpc.Client implements IClientServiceCl public gestaltsGestaltGetByIdentity(request: polykey_v1_identities_identities_pb.Provider, callback: (error: grpc.ServiceError | null, response: polykey_v1_gestalts_gestalts_pb.Graph) => void): grpc.ClientUnaryCall; public gestaltsGestaltGetByIdentity(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_gestalts_gestalts_pb.Graph) => void): grpc.ClientUnaryCall; public gestaltsGestaltGetByIdentity(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_gestalts_gestalts_pb.Graph) => void): grpc.ClientUnaryCall; + public gestaltsGestaltTrustByNode(request: polykey_v1_nodes_nodes_pb.Node, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public gestaltsGestaltTrustByNode(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public gestaltsGestaltTrustByNode(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public gestaltsGestaltTrustByIdentity(request: polykey_v1_identities_identities_pb.Provider, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public gestaltsGestaltTrustByIdentity(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; + public gestaltsGestaltTrustByIdentity(request: polykey_v1_identities_identities_pb.Provider, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public gestaltsDiscoveryByNode(request: polykey_v1_nodes_nodes_pb.Node, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public gestaltsDiscoveryByNode(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; public gestaltsDiscoveryByNode(request: polykey_v1_nodes_nodes_pb.Node, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_utils_utils_pb.EmptyMessage) => void): grpc.ClientUnaryCall; diff --git a/src/proto/js/polykey/v1/client_service_grpc_pb.js b/src/proto/js/polykey/v1/client_service_grpc_pb.js index 1db781fc8..59bfc3d72 100644 --- a/src/proto/js/polykey/v1/client_service_grpc_pb.js +++ b/src/proto/js/polykey/v1/client_service_grpc_pb.js @@ -80,6 +80,17 @@ function deserialize_polykey_v1_identities_Info(buffer_arg) { return polykey_v1_identities_identities_pb.Info.deserializeBinary(new Uint8Array(buffer_arg)); } +function serialize_polykey_v1_identities_OptionalProvider(arg) { + if (!(arg instanceof polykey_v1_identities_identities_pb.OptionalProvider)) { + throw new Error('Expected argument of type polykey.v1.identities.OptionalProvider'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_polykey_v1_identities_OptionalProvider(buffer_arg) { + return polykey_v1_identities_identities_pb.OptionalProvider.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_polykey_v1_identities_Provider(arg) { if (!(arg instanceof polykey_v1_identities_identities_pb.Provider)) { throw new Error('Expected argument of type polykey.v1.identities.Provider'); @@ -934,6 +945,17 @@ identitiesAuthenticate: { responseSerialize: serialize_polykey_v1_identities_AuthenticationProcess, responseDeserialize: deserialize_polykey_v1_identities_AuthenticationProcess, }, + identitiesAuthenticatedGet: { + path: '/polykey.v1.ClientService/IdentitiesAuthenticatedGet', + requestStream: false, + responseStream: true, + requestType: polykey_v1_identities_identities_pb.OptionalProvider, + responseType: polykey_v1_identities_identities_pb.Provider, + requestSerialize: serialize_polykey_v1_identities_OptionalProvider, + requestDeserialize: deserialize_polykey_v1_identities_OptionalProvider, + responseSerialize: serialize_polykey_v1_identities_Provider, + responseDeserialize: deserialize_polykey_v1_identities_Provider, + }, identitiesTokenPut: { path: '/polykey.v1.ClientService/IdentitiesTokenPut', requestStream: false, @@ -981,16 +1003,16 @@ identitiesAuthenticate: { identitiesInfoGet: { path: '/polykey.v1.ClientService/IdentitiesInfoGet', requestStream: false, - responseStream: false, - requestType: polykey_v1_identities_identities_pb.Provider, - responseType: polykey_v1_identities_identities_pb.Provider, - requestSerialize: serialize_polykey_v1_identities_Provider, - requestDeserialize: deserialize_polykey_v1_identities_Provider, - responseSerialize: serialize_polykey_v1_identities_Provider, - responseDeserialize: deserialize_polykey_v1_identities_Provider, + responseStream: true, + requestType: polykey_v1_identities_identities_pb.ProviderSearch, + responseType: polykey_v1_identities_identities_pb.Info, + requestSerialize: serialize_polykey_v1_identities_ProviderSearch, + requestDeserialize: deserialize_polykey_v1_identities_ProviderSearch, + responseSerialize: serialize_polykey_v1_identities_Info, + responseDeserialize: deserialize_polykey_v1_identities_Info, }, - identitiesInfoGetConnected: { - path: '/polykey.v1.ClientService/IdentitiesInfoGetConnected', + identitiesInfoConnectedGet: { + path: '/polykey.v1.ClientService/IdentitiesInfoConnectedGet', requestStream: false, responseStream: true, requestType: polykey_v1_identities_identities_pb.ProviderSearch, @@ -1045,6 +1067,28 @@ gestaltsGestaltList: { responseSerialize: serialize_polykey_v1_gestalts_Graph, responseDeserialize: deserialize_polykey_v1_gestalts_Graph, }, + gestaltsGestaltTrustByNode: { + path: '/polykey.v1.ClientService/GestaltsGestaltTrustByNode', + requestStream: false, + responseStream: false, + requestType: polykey_v1_nodes_nodes_pb.Node, + responseType: polykey_v1_utils_utils_pb.EmptyMessage, + requestSerialize: serialize_polykey_v1_nodes_Node, + requestDeserialize: deserialize_polykey_v1_nodes_Node, + responseSerialize: serialize_polykey_v1_utils_EmptyMessage, + responseDeserialize: deserialize_polykey_v1_utils_EmptyMessage, + }, + gestaltsGestaltTrustByIdentity: { + path: '/polykey.v1.ClientService/GestaltsGestaltTrustByIdentity', + requestStream: false, + responseStream: false, + requestType: polykey_v1_identities_identities_pb.Provider, + responseType: polykey_v1_utils_utils_pb.EmptyMessage, + requestSerialize: serialize_polykey_v1_identities_Provider, + requestDeserialize: deserialize_polykey_v1_identities_Provider, + responseSerialize: serialize_polykey_v1_utils_EmptyMessage, + responseDeserialize: deserialize_polykey_v1_utils_EmptyMessage, + }, gestaltsDiscoveryByNode: { path: '/polykey.v1.ClientService/GestaltsDiscoveryByNode', requestStream: false, diff --git a/src/proto/js/polykey/v1/identities/identities_pb.d.ts b/src/proto/js/polykey/v1/identities/identities_pb.d.ts index 449a7e310..c80a02774 100644 --- a/src/proto/js/polykey/v1/identities/identities_pb.d.ts +++ b/src/proto/js/polykey/v1/identities/identities_pb.d.ts @@ -29,30 +29,35 @@ export namespace Provider { } } -export class TokenSpecific extends jspb.Message { +export class OptionalProvider extends jspb.Message { - hasProvider(): boolean; - clearProvider(): void; - getProvider(): Provider | undefined; - setProvider(value?: Provider): TokenSpecific; - getToken(): string; - setToken(value: string): TokenSpecific; + hasProviderId(): boolean; + clearProviderId(): void; + getProviderId(): string; + setProviderId(value: string): OptionalProvider; + + getOptionalProviderIdCase(): OptionalProvider.OptionalProviderIdCase; serializeBinary(): Uint8Array; - toObject(includeInstance?: boolean): TokenSpecific.AsObject; - static toObject(includeInstance: boolean, msg: TokenSpecific): TokenSpecific.AsObject; + toObject(includeInstance?: boolean): OptionalProvider.AsObject; + static toObject(includeInstance: boolean, msg: OptionalProvider): OptionalProvider.AsObject; static extensions: {[key: number]: jspb.ExtensionFieldInfo}; static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; - static serializeBinaryToWriter(message: TokenSpecific, writer: jspb.BinaryWriter): void; - static deserializeBinary(bytes: Uint8Array): TokenSpecific; - static deserializeBinaryFromReader(message: TokenSpecific, reader: jspb.BinaryReader): TokenSpecific; + static serializeBinaryToWriter(message: OptionalProvider, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): OptionalProvider; + static deserializeBinaryFromReader(message: OptionalProvider, reader: jspb.BinaryReader): OptionalProvider; } -export namespace TokenSpecific { +export namespace OptionalProvider { export type AsObject = { - provider?: Provider.AsObject, - token: string, + providerId: string, } + + export enum OptionalProviderIdCase { + OPTIONAL_PROVIDER_ID_NOT_SET = 0, + PROVIDER_ID = 1, + } + } export class Token extends jspb.Message { @@ -75,6 +80,32 @@ export namespace Token { } } +export class TokenSpecific extends jspb.Message { + + hasProvider(): boolean; + clearProvider(): void; + getProvider(): Provider | undefined; + setProvider(value?: Provider): TokenSpecific; + getToken(): string; + setToken(value: string): TokenSpecific; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): TokenSpecific.AsObject; + static toObject(includeInstance: boolean, msg: TokenSpecific): TokenSpecific.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: TokenSpecific, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): TokenSpecific; + static deserializeBinaryFromReader(message: TokenSpecific, reader: jspb.BinaryReader): TokenSpecific; +} + +export namespace TokenSpecific { + export type AsObject = { + provider?: Provider.AsObject, + token: string, + } +} + export class Claim extends jspb.Message { getClaimId(): string; setClaimId(value: string): Claim; @@ -183,14 +214,26 @@ export namespace AuthenticationResponse { export class ProviderSearch extends jspb.Message { - hasProvider(): boolean; - clearProvider(): void; - getProvider(): Provider | undefined; - setProvider(value?: Provider): ProviderSearch; + hasAuthIdentityId(): boolean; + clearAuthIdentityId(): void; + getAuthIdentityId(): string; + setAuthIdentityId(value: string): ProviderSearch; + getIdentityId(): string; + setIdentityId(value: string): ProviderSearch; + getDisconnected(): boolean; + setDisconnected(value: boolean): ProviderSearch; + getLimit(): string; + setLimit(value: string): ProviderSearch; clearSearchTermList(): void; getSearchTermList(): Array; setSearchTermList(value: Array): ProviderSearch; addSearchTerm(value: string, index?: number): string; + clearProviderIdList(): void; + getProviderIdList(): Array; + setProviderIdList(value: Array): ProviderSearch; + addProviderId(value: string, index?: number): string; + + getOptionalAuthIdentityIdCase(): ProviderSearch.OptionalAuthIdentityIdCase; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): ProviderSearch.AsObject; @@ -204,9 +247,19 @@ export class ProviderSearch extends jspb.Message { export namespace ProviderSearch { export type AsObject = { - provider?: Provider.AsObject, + authIdentityId: string, + identityId: string, + disconnected: boolean, + limit: string, searchTermList: Array, + providerIdList: Array, } + + export enum OptionalAuthIdentityIdCase { + OPTIONAL_AUTH_IDENTITY_ID_NOT_SET = 0, + AUTH_IDENTITY_ID = 1, + } + } export class Info extends jspb.Message { diff --git a/src/proto/js/polykey/v1/identities/identities_pb.js b/src/proto/js/polykey/v1/identities/identities_pb.js index 64290d1a4..cbfb21ed9 100644 --- a/src/proto/js/polykey/v1/identities/identities_pb.js +++ b/src/proto/js/polykey/v1/identities/identities_pb.js @@ -20,8 +20,11 @@ goog.exportSymbol('proto.polykey.v1.identities.AuthenticationRequest', null, glo goog.exportSymbol('proto.polykey.v1.identities.AuthenticationResponse', null, global); goog.exportSymbol('proto.polykey.v1.identities.Claim', null, global); goog.exportSymbol('proto.polykey.v1.identities.Info', null, global); +goog.exportSymbol('proto.polykey.v1.identities.OptionalProvider', null, global); +goog.exportSymbol('proto.polykey.v1.identities.OptionalProvider.OptionalProviderIdCase', null, global); goog.exportSymbol('proto.polykey.v1.identities.Provider', null, global); goog.exportSymbol('proto.polykey.v1.identities.ProviderSearch', null, global); +goog.exportSymbol('proto.polykey.v1.identities.ProviderSearch.OptionalAuthIdentityIdCase', null, global); goog.exportSymbol('proto.polykey.v1.identities.Token', null, global); goog.exportSymbol('proto.polykey.v1.identities.TokenSpecific', null, global); /** @@ -55,16 +58,16 @@ if (goog.DEBUG && !COMPILED) { * @extends {jspb.Message} * @constructor */ -proto.polykey.v1.identities.TokenSpecific = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, null, null); +proto.polykey.v1.identities.OptionalProvider = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.polykey.v1.identities.OptionalProvider.oneofGroups_); }; -goog.inherits(proto.polykey.v1.identities.TokenSpecific, jspb.Message); +goog.inherits(proto.polykey.v1.identities.OptionalProvider, jspb.Message); if (goog.DEBUG && !COMPILED) { /** * @public * @override */ - proto.polykey.v1.identities.TokenSpecific.displayName = 'proto.polykey.v1.identities.TokenSpecific'; + proto.polykey.v1.identities.OptionalProvider.displayName = 'proto.polykey.v1.identities.OptionalProvider'; } /** * Generated by JsPbCodeGenerator. @@ -87,6 +90,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.polykey.v1.identities.Token.displayName = 'proto.polykey.v1.identities.Token'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.polykey.v1.identities.TokenSpecific = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.polykey.v1.identities.TokenSpecific, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.polykey.v1.identities.TokenSpecific.displayName = 'proto.polykey.v1.identities.TokenSpecific'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -182,7 +206,7 @@ if (goog.DEBUG && !COMPILED) { * @constructor */ proto.polykey.v1.identities.ProviderSearch = function(opt_data) { - jspb.Message.initialize(this, opt_data, 0, -1, proto.polykey.v1.identities.ProviderSearch.repeatedFields_, null); + jspb.Message.initialize(this, opt_data, 0, -1, proto.polykey.v1.identities.ProviderSearch.repeatedFields_, proto.polykey.v1.identities.ProviderSearch.oneofGroups_); }; goog.inherits(proto.polykey.v1.identities.ProviderSearch, jspb.Message); if (goog.DEBUG && !COMPILED) { @@ -374,6 +398,31 @@ proto.polykey.v1.identities.Provider.prototype.setIdentityId = function(value) { +/** + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const + */ +proto.polykey.v1.identities.OptionalProvider.oneofGroups_ = [[1]]; + +/** + * @enum {number} + */ +proto.polykey.v1.identities.OptionalProvider.OptionalProviderIdCase = { + OPTIONAL_PROVIDER_ID_NOT_SET: 0, + PROVIDER_ID: 1 +}; + +/** + * @return {proto.polykey.v1.identities.OptionalProvider.OptionalProviderIdCase} + */ +proto.polykey.v1.identities.OptionalProvider.prototype.getOptionalProviderIdCase = function() { + return /** @type {proto.polykey.v1.identities.OptionalProvider.OptionalProviderIdCase} */(jspb.Message.computeOneofCase(this, proto.polykey.v1.identities.OptionalProvider.oneofGroups_[0])); +}; + if (jspb.Message.GENERATE_TO_OBJECT) { @@ -389,8 +438,8 @@ if (jspb.Message.GENERATE_TO_OBJECT) { * http://goto/soy-param-migration * @return {!Object} */ -proto.polykey.v1.identities.TokenSpecific.prototype.toObject = function(opt_includeInstance) { - return proto.polykey.v1.identities.TokenSpecific.toObject(opt_includeInstance, this); +proto.polykey.v1.identities.OptionalProvider.prototype.toObject = function(opt_includeInstance) { + return proto.polykey.v1.identities.OptionalProvider.toObject(opt_includeInstance, this); }; @@ -399,14 +448,13 @@ proto.polykey.v1.identities.TokenSpecific.prototype.toObject = function(opt_incl * @param {boolean|undefined} includeInstance Deprecated. Whether to include * the JSPB instance for transitional soy proto support: * http://goto/soy-param-migration - * @param {!proto.polykey.v1.identities.TokenSpecific} msg The msg instance to transform. + * @param {!proto.polykey.v1.identities.OptionalProvider} msg The msg instance to transform. * @return {!Object} * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.polykey.v1.identities.TokenSpecific.toObject = function(includeInstance, msg) { +proto.polykey.v1.identities.OptionalProvider.toObject = function(includeInstance, msg) { var f, obj = { - provider: (f = msg.getProvider()) && proto.polykey.v1.identities.Provider.toObject(includeInstance, f), - token: jspb.Message.getFieldWithDefault(msg, 2, "") + providerId: jspb.Message.getFieldWithDefault(msg, 1, "") }; if (includeInstance) { @@ -420,23 +468,23 @@ proto.polykey.v1.identities.TokenSpecific.toObject = function(includeInstance, m /** * Deserializes binary data (in protobuf wire format). * @param {jspb.ByteSource} bytes The bytes to deserialize. - * @return {!proto.polykey.v1.identities.TokenSpecific} + * @return {!proto.polykey.v1.identities.OptionalProvider} */ -proto.polykey.v1.identities.TokenSpecific.deserializeBinary = function(bytes) { +proto.polykey.v1.identities.OptionalProvider.deserializeBinary = function(bytes) { var reader = new jspb.BinaryReader(bytes); - var msg = new proto.polykey.v1.identities.TokenSpecific; - return proto.polykey.v1.identities.TokenSpecific.deserializeBinaryFromReader(msg, reader); + var msg = new proto.polykey.v1.identities.OptionalProvider; + return proto.polykey.v1.identities.OptionalProvider.deserializeBinaryFromReader(msg, reader); }; /** * Deserializes binary data (in protobuf wire format) from the * given reader into the given message object. - * @param {!proto.polykey.v1.identities.TokenSpecific} msg The message object to deserialize into. + * @param {!proto.polykey.v1.identities.OptionalProvider} msg The message object to deserialize into. * @param {!jspb.BinaryReader} reader The BinaryReader to use. - * @return {!proto.polykey.v1.identities.TokenSpecific} + * @return {!proto.polykey.v1.identities.OptionalProvider} */ -proto.polykey.v1.identities.TokenSpecific.deserializeBinaryFromReader = function(msg, reader) { +proto.polykey.v1.identities.OptionalProvider.deserializeBinaryFromReader = function(msg, reader) { while (reader.nextField()) { if (reader.isEndGroup()) { break; @@ -444,13 +492,8 @@ proto.polykey.v1.identities.TokenSpecific.deserializeBinaryFromReader = function var field = reader.getFieldNumber(); switch (field) { case 1: - var value = new proto.polykey.v1.identities.Provider; - reader.readMessage(value,proto.polykey.v1.identities.Provider.deserializeBinaryFromReader); - msg.setProvider(value); - break; - case 2: var value = /** @type {string} */ (reader.readString()); - msg.setToken(value); + msg.setProviderId(value); break; default: reader.skipField(); @@ -465,9 +508,9 @@ proto.polykey.v1.identities.TokenSpecific.deserializeBinaryFromReader = function * Serializes the message to binary data (in protobuf wire format). * @return {!Uint8Array} */ -proto.polykey.v1.identities.TokenSpecific.prototype.serializeBinary = function() { +proto.polykey.v1.identities.OptionalProvider.prototype.serializeBinary = function() { var writer = new jspb.BinaryWriter(); - proto.polykey.v1.identities.TokenSpecific.serializeBinaryToWriter(this, writer); + proto.polykey.v1.identities.OptionalProvider.serializeBinaryToWriter(this, writer); return writer.getResultBuffer(); }; @@ -475,24 +518,16 @@ proto.polykey.v1.identities.TokenSpecific.prototype.serializeBinary = function() /** * Serializes the given message to binary data (in protobuf wire * format), writing to the given BinaryWriter. - * @param {!proto.polykey.v1.identities.TokenSpecific} message + * @param {!proto.polykey.v1.identities.OptionalProvider} message * @param {!jspb.BinaryWriter} writer * @suppress {unusedLocalVariables} f is only used for nested messages */ -proto.polykey.v1.identities.TokenSpecific.serializeBinaryToWriter = function(message, writer) { +proto.polykey.v1.identities.OptionalProvider.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getProvider(); + f = /** @type {string} */ (jspb.Message.getField(message, 1)); if (f != null) { - writer.writeMessage( - 1, - f, - proto.polykey.v1.identities.Provider.serializeBinaryToWriter - ); - } - f = message.getToken(); - if (f.length > 0) { writer.writeString( - 2, + 1, f ); } @@ -500,30 +535,29 @@ proto.polykey.v1.identities.TokenSpecific.serializeBinaryToWriter = function(mes /** - * optional Provider provider = 1; - * @return {?proto.polykey.v1.identities.Provider} + * optional string provider_id = 1; + * @return {string} */ -proto.polykey.v1.identities.TokenSpecific.prototype.getProvider = function() { - return /** @type{?proto.polykey.v1.identities.Provider} */ ( - jspb.Message.getWrapperField(this, proto.polykey.v1.identities.Provider, 1)); +proto.polykey.v1.identities.OptionalProvider.prototype.getProviderId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); }; /** - * @param {?proto.polykey.v1.identities.Provider|undefined} value - * @return {!proto.polykey.v1.identities.TokenSpecific} returns this -*/ -proto.polykey.v1.identities.TokenSpecific.prototype.setProvider = function(value) { - return jspb.Message.setWrapperField(this, 1, value); + * @param {string} value + * @return {!proto.polykey.v1.identities.OptionalProvider} returns this + */ +proto.polykey.v1.identities.OptionalProvider.prototype.setProviderId = function(value) { + return jspb.Message.setOneofField(this, 1, proto.polykey.v1.identities.OptionalProvider.oneofGroups_[0], value); }; /** - * Clears the message field making it undefined. - * @return {!proto.polykey.v1.identities.TokenSpecific} returns this + * Clears the field making it undefined. + * @return {!proto.polykey.v1.identities.OptionalProvider} returns this */ -proto.polykey.v1.identities.TokenSpecific.prototype.clearProvider = function() { - return this.setProvider(undefined); +proto.polykey.v1.identities.OptionalProvider.prototype.clearProviderId = function() { + return jspb.Message.setOneofField(this, 1, proto.polykey.v1.identities.OptionalProvider.oneofGroups_[0], undefined); }; @@ -531,29 +565,11 @@ proto.polykey.v1.identities.TokenSpecific.prototype.clearProvider = function() { * Returns whether this field is set. * @return {boolean} */ -proto.polykey.v1.identities.TokenSpecific.prototype.hasProvider = function() { +proto.polykey.v1.identities.OptionalProvider.prototype.hasProviderId = function() { return jspb.Message.getField(this, 1) != null; }; -/** - * optional string token = 2; - * @return {string} - */ -proto.polykey.v1.identities.TokenSpecific.prototype.getToken = function() { - return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); -}; - - -/** - * @param {string} value - * @return {!proto.polykey.v1.identities.TokenSpecific} returns this - */ -proto.polykey.v1.identities.TokenSpecific.prototype.setToken = function(value) { - return jspb.Message.setProto3StringField(this, 2, value); -}; - - @@ -687,6 +703,187 @@ proto.polykey.v1.identities.Token.prototype.setToken = function(value) { +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.polykey.v1.identities.TokenSpecific.prototype.toObject = function(opt_includeInstance) { + return proto.polykey.v1.identities.TokenSpecific.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.polykey.v1.identities.TokenSpecific} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.identities.TokenSpecific.toObject = function(includeInstance, msg) { + var f, obj = { + provider: (f = msg.getProvider()) && proto.polykey.v1.identities.Provider.toObject(includeInstance, f), + token: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.polykey.v1.identities.TokenSpecific} + */ +proto.polykey.v1.identities.TokenSpecific.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.polykey.v1.identities.TokenSpecific; + return proto.polykey.v1.identities.TokenSpecific.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.polykey.v1.identities.TokenSpecific} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.polykey.v1.identities.TokenSpecific} + */ +proto.polykey.v1.identities.TokenSpecific.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new proto.polykey.v1.identities.Provider; + reader.readMessage(value,proto.polykey.v1.identities.Provider.deserializeBinaryFromReader); + msg.setProvider(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setToken(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.polykey.v1.identities.TokenSpecific.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.polykey.v1.identities.TokenSpecific.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.polykey.v1.identities.TokenSpecific} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.identities.TokenSpecific.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getProvider(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.polykey.v1.identities.Provider.serializeBinaryToWriter + ); + } + f = message.getToken(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * optional Provider provider = 1; + * @return {?proto.polykey.v1.identities.Provider} + */ +proto.polykey.v1.identities.TokenSpecific.prototype.getProvider = function() { + return /** @type{?proto.polykey.v1.identities.Provider} */ ( + jspb.Message.getWrapperField(this, proto.polykey.v1.identities.Provider, 1)); +}; + + +/** + * @param {?proto.polykey.v1.identities.Provider|undefined} value + * @return {!proto.polykey.v1.identities.TokenSpecific} returns this +*/ +proto.polykey.v1.identities.TokenSpecific.prototype.setProvider = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.polykey.v1.identities.TokenSpecific} returns this + */ +proto.polykey.v1.identities.TokenSpecific.prototype.clearProvider = function() { + return this.setProvider(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.polykey.v1.identities.TokenSpecific.prototype.hasProvider = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional string token = 2; + * @return {string} + */ +proto.polykey.v1.identities.TokenSpecific.prototype.getToken = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.polykey.v1.identities.TokenSpecific} returns this + */ +proto.polykey.v1.identities.TokenSpecific.prototype.setToken = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + + + + if (jspb.Message.GENERATE_TO_OBJECT) { /** * Creates an object representation of this proto. @@ -1371,7 +1568,32 @@ proto.polykey.v1.identities.AuthenticationResponse.prototype.setIdentityId = fun * @private {!Array} * @const */ -proto.polykey.v1.identities.ProviderSearch.repeatedFields_ = [2]; +proto.polykey.v1.identities.ProviderSearch.repeatedFields_ = [5,6]; + +/** + * Oneof group definitions for this message. Each group defines the field + * numbers belonging to that group. When of these fields' value is set, all + * other fields in the group are cleared. During deserialization, if multiple + * fields are encountered for a group, only the last value seen will be kept. + * @private {!Array>} + * @const + */ +proto.polykey.v1.identities.ProviderSearch.oneofGroups_ = [[1]]; + +/** + * @enum {number} + */ +proto.polykey.v1.identities.ProviderSearch.OptionalAuthIdentityIdCase = { + OPTIONAL_AUTH_IDENTITY_ID_NOT_SET: 0, + AUTH_IDENTITY_ID: 1 +}; + +/** + * @return {proto.polykey.v1.identities.ProviderSearch.OptionalAuthIdentityIdCase} + */ +proto.polykey.v1.identities.ProviderSearch.prototype.getOptionalAuthIdentityIdCase = function() { + return /** @type {proto.polykey.v1.identities.ProviderSearch.OptionalAuthIdentityIdCase} */(jspb.Message.computeOneofCase(this, proto.polykey.v1.identities.ProviderSearch.oneofGroups_[0])); +}; @@ -1404,8 +1626,12 @@ proto.polykey.v1.identities.ProviderSearch.prototype.toObject = function(opt_inc */ proto.polykey.v1.identities.ProviderSearch.toObject = function(includeInstance, msg) { var f, obj = { - provider: (f = msg.getProvider()) && proto.polykey.v1.identities.Provider.toObject(includeInstance, f), - searchTermList: (f = jspb.Message.getRepeatedField(msg, 2)) == null ? undefined : f + authIdentityId: jspb.Message.getFieldWithDefault(msg, 1, ""), + identityId: jspb.Message.getFieldWithDefault(msg, 2, ""), + disconnected: jspb.Message.getBooleanFieldWithDefault(msg, 3, false), + limit: jspb.Message.getFieldWithDefault(msg, 4, ""), + searchTermList: (f = jspb.Message.getRepeatedField(msg, 5)) == null ? undefined : f, + providerIdList: (f = jspb.Message.getRepeatedField(msg, 6)) == null ? undefined : f }; if (includeInstance) { @@ -1443,14 +1669,29 @@ proto.polykey.v1.identities.ProviderSearch.deserializeBinaryFromReader = functio var field = reader.getFieldNumber(); switch (field) { case 1: - var value = new proto.polykey.v1.identities.Provider; - reader.readMessage(value,proto.polykey.v1.identities.Provider.deserializeBinaryFromReader); - msg.setProvider(value); + var value = /** @type {string} */ (reader.readString()); + msg.setAuthIdentityId(value); break; case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setIdentityId(value); + break; + case 3: + var value = /** @type {boolean} */ (reader.readBool()); + msg.setDisconnected(value); + break; + case 4: + var value = /** @type {string} */ (reader.readString()); + msg.setLimit(value); + break; + case 5: var value = /** @type {string} */ (reader.readString()); msg.addSearchTerm(value); break; + case 6: + var value = /** @type {string} */ (reader.readString()); + msg.addProviderId(value); + break; default: reader.skipField(); break; @@ -1480,18 +1721,45 @@ proto.polykey.v1.identities.ProviderSearch.prototype.serializeBinary = function( */ proto.polykey.v1.identities.ProviderSearch.serializeBinaryToWriter = function(message, writer) { var f = undefined; - f = message.getProvider(); + f = /** @type {string} */ (jspb.Message.getField(message, 1)); if (f != null) { - writer.writeMessage( + writer.writeString( 1, - f, - proto.polykey.v1.identities.Provider.serializeBinaryToWriter + f + ); + } + f = message.getIdentityId(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getDisconnected(); + if (f) { + writer.writeBool( + 3, + f + ); + } + f = message.getLimit(); + if (f.length > 0) { + writer.writeString( + 4, + f ); } f = message.getSearchTermList(); if (f.length > 0) { writer.writeRepeatedString( - 2, + 5, + f + ); + } + f = message.getProviderIdList(); + if (f.length > 0) { + writer.writeRepeatedString( + 6, f ); } @@ -1499,30 +1767,29 @@ proto.polykey.v1.identities.ProviderSearch.serializeBinaryToWriter = function(me /** - * optional Provider provider = 1; - * @return {?proto.polykey.v1.identities.Provider} + * optional string auth_identity_id = 1; + * @return {string} */ -proto.polykey.v1.identities.ProviderSearch.prototype.getProvider = function() { - return /** @type{?proto.polykey.v1.identities.Provider} */ ( - jspb.Message.getWrapperField(this, proto.polykey.v1.identities.Provider, 1)); +proto.polykey.v1.identities.ProviderSearch.prototype.getAuthIdentityId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); }; /** - * @param {?proto.polykey.v1.identities.Provider|undefined} value + * @param {string} value * @return {!proto.polykey.v1.identities.ProviderSearch} returns this -*/ -proto.polykey.v1.identities.ProviderSearch.prototype.setProvider = function(value) { - return jspb.Message.setWrapperField(this, 1, value); + */ +proto.polykey.v1.identities.ProviderSearch.prototype.setAuthIdentityId = function(value) { + return jspb.Message.setOneofField(this, 1, proto.polykey.v1.identities.ProviderSearch.oneofGroups_[0], value); }; /** - * Clears the message field making it undefined. + * Clears the field making it undefined. * @return {!proto.polykey.v1.identities.ProviderSearch} returns this */ -proto.polykey.v1.identities.ProviderSearch.prototype.clearProvider = function() { - return this.setProvider(undefined); +proto.polykey.v1.identities.ProviderSearch.prototype.clearAuthIdentityId = function() { + return jspb.Message.setOneofField(this, 1, proto.polykey.v1.identities.ProviderSearch.oneofGroups_[0], undefined); }; @@ -1530,17 +1797,71 @@ proto.polykey.v1.identities.ProviderSearch.prototype.clearProvider = function() * Returns whether this field is set. * @return {boolean} */ -proto.polykey.v1.identities.ProviderSearch.prototype.hasProvider = function() { +proto.polykey.v1.identities.ProviderSearch.prototype.hasAuthIdentityId = function() { return jspb.Message.getField(this, 1) != null; }; /** - * repeated string search_term = 2; + * optional string identity_id = 2; + * @return {string} + */ +proto.polykey.v1.identities.ProviderSearch.prototype.getIdentityId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.polykey.v1.identities.ProviderSearch} returns this + */ +proto.polykey.v1.identities.ProviderSearch.prototype.setIdentityId = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional bool disconnected = 3; + * @return {boolean} + */ +proto.polykey.v1.identities.ProviderSearch.prototype.getDisconnected = function() { + return /** @type {boolean} */ (jspb.Message.getBooleanFieldWithDefault(this, 3, false)); +}; + + +/** + * @param {boolean} value + * @return {!proto.polykey.v1.identities.ProviderSearch} returns this + */ +proto.polykey.v1.identities.ProviderSearch.prototype.setDisconnected = function(value) { + return jspb.Message.setProto3BooleanField(this, 3, value); +}; + + +/** + * optional string limit = 4; + * @return {string} + */ +proto.polykey.v1.identities.ProviderSearch.prototype.getLimit = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 4, "")); +}; + + +/** + * @param {string} value + * @return {!proto.polykey.v1.identities.ProviderSearch} returns this + */ +proto.polykey.v1.identities.ProviderSearch.prototype.setLimit = function(value) { + return jspb.Message.setProto3StringField(this, 4, value); +}; + + +/** + * repeated string search_term = 5; * @return {!Array} */ proto.polykey.v1.identities.ProviderSearch.prototype.getSearchTermList = function() { - return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 2)); + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 5)); }; @@ -1549,7 +1870,7 @@ proto.polykey.v1.identities.ProviderSearch.prototype.getSearchTermList = functio * @return {!proto.polykey.v1.identities.ProviderSearch} returns this */ proto.polykey.v1.identities.ProviderSearch.prototype.setSearchTermList = function(value) { - return jspb.Message.setField(this, 2, value || []); + return jspb.Message.setField(this, 5, value || []); }; @@ -1559,7 +1880,7 @@ proto.polykey.v1.identities.ProviderSearch.prototype.setSearchTermList = functio * @return {!proto.polykey.v1.identities.ProviderSearch} returns this */ proto.polykey.v1.identities.ProviderSearch.prototype.addSearchTerm = function(value, opt_index) { - return jspb.Message.addToRepeatedField(this, 2, value, opt_index); + return jspb.Message.addToRepeatedField(this, 5, value, opt_index); }; @@ -1572,6 +1893,43 @@ proto.polykey.v1.identities.ProviderSearch.prototype.clearSearchTermList = funct }; +/** + * repeated string provider_id = 6; + * @return {!Array} + */ +proto.polykey.v1.identities.ProviderSearch.prototype.getProviderIdList = function() { + return /** @type {!Array} */ (jspb.Message.getRepeatedField(this, 6)); +}; + + +/** + * @param {!Array} value + * @return {!proto.polykey.v1.identities.ProviderSearch} returns this + */ +proto.polykey.v1.identities.ProviderSearch.prototype.setProviderIdList = function(value) { + return jspb.Message.setField(this, 6, value || []); +}; + + +/** + * @param {string} value + * @param {number=} opt_index + * @return {!proto.polykey.v1.identities.ProviderSearch} returns this + */ +proto.polykey.v1.identities.ProviderSearch.prototype.addProviderId = function(value, opt_index) { + return jspb.Message.addToRepeatedField(this, 6, value, opt_index); +}; + + +/** + * Clears the list making it empty but non-null. + * @return {!proto.polykey.v1.identities.ProviderSearch} returns this + */ +proto.polykey.v1.identities.ProviderSearch.prototype.clearProviderIdList = function() { + return this.setProviderIdList([]); +}; + + diff --git a/src/proto/schemas/polykey/v1/client_service.proto b/src/proto/schemas/polykey/v1/client_service.proto index f2dca62c1..eb5aa9ced 100644 --- a/src/proto/schemas/polykey/v1/client_service.proto +++ b/src/proto/schemas/polykey/v1/client_service.proto @@ -64,18 +64,21 @@ service ClientService { // Identities rpc IdentitiesAuthenticate(polykey.v1.identities.Provider) returns (stream polykey.v1.identities.AuthenticationProcess); + rpc IdentitiesAuthenticatedGet(polykey.v1.identities.OptionalProvider) returns (stream polykey.v1.identities.Provider); rpc IdentitiesTokenPut(polykey.v1.identities.TokenSpecific) returns (polykey.v1.utils.EmptyMessage); rpc IdentitiesTokenGet(polykey.v1.identities.Provider) returns (polykey.v1.identities.Token); rpc IdentitiesTokenDelete(polykey.v1.identities.Provider) returns (polykey.v1.utils.EmptyMessage); rpc IdentitiesProvidersList(polykey.v1.utils.EmptyMessage) returns (polykey.v1.identities.Provider); - rpc IdentitiesInfoGet(polykey.v1.identities.Provider) returns (polykey.v1.identities.Provider); - rpc IdentitiesInfoGetConnected(polykey.v1.identities.ProviderSearch) returns (stream polykey.v1.identities.Info); + rpc IdentitiesInfoGet(polykey.v1.identities.ProviderSearch) returns (stream polykey.v1.identities.Info); + rpc IdentitiesInfoConnectedGet(polykey.v1.identities.ProviderSearch) returns (stream polykey.v1.identities.Info); rpc IdentitiesClaim(polykey.v1.identities.Provider) returns (polykey.v1.identities.Claim); // Gestalts rpc GestaltsGestaltList(polykey.v1.utils.EmptyMessage) returns (stream polykey.v1.gestalts.Gestalt); rpc GestaltsGestaltGetByNode(polykey.v1.nodes.Node) returns (polykey.v1.gestalts.Graph); rpc GestaltsGestaltGetByIdentity(polykey.v1.identities.Provider) returns (polykey.v1.gestalts.Graph); + rpc GestaltsGestaltTrustByNode(polykey.v1.nodes.Node) returns (polykey.v1.utils.EmptyMessage); + rpc GestaltsGestaltTrustByIdentity(polykey.v1.identities.Provider) returns (polykey.v1.utils.EmptyMessage); rpc GestaltsDiscoveryByNode(polykey.v1.nodes.Node) returns (polykey.v1.utils.EmptyMessage); rpc GestaltsDiscoveryByIdentity(polykey.v1.identities.Provider) returns (polykey.v1.utils.EmptyMessage); rpc GestaltsActionsGetByNode(polykey.v1.nodes.Node) returns (polykey.v1.permissions.Actions); diff --git a/src/proto/schemas/polykey/v1/identities/identities.proto b/src/proto/schemas/polykey/v1/identities/identities.proto index 0df20034f..a671642dc 100644 --- a/src/proto/schemas/polykey/v1/identities/identities.proto +++ b/src/proto/schemas/polykey/v1/identities/identities.proto @@ -6,16 +6,21 @@ message Provider { string provider_id = 1; string identity_id = 2; } - -message TokenSpecific { - Provider provider = 1; - string token = 2; +message OptionalProvider { + oneof optional_provider_id { + string provider_id = 1; + } } message Token { string token = 1; } +message TokenSpecific { + Provider provider = 1; + string token = 2; +} + message Claim { string claim_id = 1; string url = 2; @@ -38,8 +43,14 @@ message AuthenticationResponse { } message ProviderSearch { - Provider provider = 1; - repeated string search_term = 2; + oneof optional_auth_identity_id { + string auth_identity_id = 1; + } + string identity_id = 2; + bool disconnected = 3; + string limit = 4; + repeated string search_term = 5; + repeated string provider_id = 6; } message Info { diff --git a/tests/bin/identities/identities.test.ts b/tests/bin/identities/identities.test.ts index 955a8f98c..1f9753a5c 100644 --- a/tests/bin/identities/identities.test.ts +++ b/tests/bin/identities/identities.test.ts @@ -1,15 +1,17 @@ import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; import type { NodeIdEncoded, NodeInfo } from '@/nodes/types'; -import type { ClaimLinkIdentity, ClaimLinkNode } from '@/claims/types'; +import type { ClaimLinkIdentity } from '@/claims/types'; +import type { Gestalt } from '@/gestalts/types'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { PolykeyAgent } from '@'; +import { poll } from '@/utils'; import * as claimsUtils from '@/claims/utils'; import * as identitiesUtils from '@/identities/utils'; -import { utils as nodesUtils } from '@/nodes'; import * as keysUtils from '@/keys/utils'; +import * as nodesUtils from '@/nodes/utils'; import * as testBinUtils from '../utils'; import * as testNodesUtils from '../../nodes/utils'; import TestProvider from '../../identities/TestProvider'; @@ -407,17 +409,8 @@ describe('CLI Identities', () => { expect(actionKeys).toContain('notify'); }); test('Should fail on invalid inputs.', async () => { - let result; - // Invalid node. - result = await testBinUtils.pkStdio( - genCommands(['trust', invaldNode.id]), - {}, - dataDir, - ); - expect(result.exitCode === 0).toBeFalsy(); // Fails.. - - // invalid identity - result = await testBinUtils.pkStdio( + // Invalid identity + const result = await testBinUtils.pkStdio( genCommands([ 'trust', identityString( @@ -481,7 +474,7 @@ describe('CLI Identities', () => { let result; // Invalid node. result = await testBinUtils.pkStdio( - genCommands(['trust', invaldNode.id]), + genCommands(['untrust', invaldNode.id]), {}, dataDir, ); @@ -563,6 +556,51 @@ describe('CLI Identities', () => { ); }); }); + describe('commandAuthenticatedGet', () => { + test('Should get authenticated identities', async () => { + // Need an authenticated identity. + await polykeyAgent.identitiesManager.putToken( + testToken.providerId, + testToken.identityId, + testToken.tokenData, + ); + const commands = ['identities', 'authenticated', '-np', nodePath]; + const result = await testBinUtils.pkStdio(commands, {}, dataDir); + expect(result.exitCode).toBe(0); // Succeeds. + expect(result.stdout).toContain(testToken.providerId); + expect(result.stdout).toContain(testToken.identityId); + // Unauthenticate identity + await polykeyAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + }); + test('Should get authenticated identities from specific provider', async () => { + // Need an authenticated identity. + await polykeyAgent.identitiesManager.putToken( + testToken.providerId, + testToken.identityId, + testToken.tokenData, + ); + const commands = [ + 'identities', + 'authenticated', + '--provider-id', + testToken.providerId, + '-np', + nodePath, + ]; + const result = await testBinUtils.pkStdio(commands, {}, dataDir); + expect(result.exitCode).toBe(0); // Succeeds. + expect(result.stdout).toContain(testToken.providerId); + expect(result.stdout).toContain(testToken.identityId); + // Unauthenticate identity + await polykeyAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + }); + }); describe('commandGetGestalts', () => { const nodeIdEncoded = node1.id; test('Should list gestalt by Node', async () => { @@ -627,9 +665,20 @@ describe('CLI Identities', () => { }); describe('commandSearchIdentities', () => { test('Should find a connected identity.', async () => { - // Create an identity + const provider = new TestProvider('provider' as ProviderId); + const identity = { + providerId: provider.id, + identityId: 'connected_user' as IdentityId, + name: 'User', + email: 'user@test.com', + url: 'test.com/user', + }; + provider.users['connected_user'] = identity; + provider.users[testToken.identityId].connected = ['connected_user']; + polykeyAgent.identitiesManager.registerProvider(provider); + // Need an authenticated identity await polykeyAgent.identitiesManager.putToken( - testToken.providerId, + provider.id, testToken.identityId, testToken.tokenData, ); @@ -638,21 +687,28 @@ describe('CLI Identities', () => { 'search', '-np', nodePath, - testToken.providerId, + '--provider-id', + 'provider', + '--format', + 'json', ]; const result = await testBinUtils.pkStdio(commands, {}, dataDir); expect(result.exitCode).toBe(0); // Succeeds. - expect(result.stdout).toContain(testToken.providerId); - expect(result.stdout).toContain(testToken.identityId); + expect(JSON.parse(result.stdout)).toEqual(identity); + await polykeyAgent.identitiesManager.delToken( + provider.id, + testToken.identityId, + ); + polykeyAgent.identitiesManager.unregisterProvider(provider.id); }); }); describe('commandDiscoverGestalts', () => { let rootDataDir; // Test variables + const testProvider = new TestProvider('discovery-provider' as ProviderId); + const identityId = 'connected-identity' as IdentityId; let nodeB: PolykeyAgent; let nodeC: PolykeyAgent; - // Let testProvider: TestProvider; - let identityId: IdentityId; beforeAll(async () => { rootDataDir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'polykey-test-'), @@ -681,26 +737,17 @@ describe('CLI Identities', () => { // Adding connection details. await testNodesUtils.nodesConnect(polykeyAgent, nodeB); await testNodesUtils.nodesConnect(nodeB, nodeC); + await testNodesUtils.nodesConnect(polykeyAgent, nodeC); // Adding sigchain details. - const claimBtoC: ClaimLinkNode = { - type: 'node', - node1: nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), - node2: nodesUtils.encodeNodeId(nodeC.keyManager.getNodeId()), - }; - const claimCtoB: ClaimLinkNode = { - type: 'node', - node1: nodesUtils.encodeNodeId(nodeC.keyManager.getNodeId()), - node2: nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), - }; - await nodeB.sigchain.addClaim(claimBtoC); - await nodeB.sigchain.addClaim(claimCtoB); - await nodeC.sigchain.addClaim(claimCtoB); - await nodeC.sigchain.addClaim(claimBtoC); + await nodeB.nodeManager.claimNode(nodeC.keyManager.getNodeId()); // Setting up identtiy. - const gen = testProvider.authenticate(); - await gen.next(); - identityId = (await gen.next()).value as IdentityId; + testProvider.users[identityId] = {}; + polykeyAgent.identitiesManager.registerProvider(testProvider); + nodeB.identitiesManager.registerProvider(testProvider); + await nodeB.identitiesManager.putToken(testProvider.id, identityId, { + accessToken: 'def456', + }); const claimIdentToB: ClaimLinkIdentity = { type: 'identity', @@ -711,7 +758,7 @@ describe('CLI Identities', () => { const [, claimEncoded] = await nodeB.sigchain.addClaim(claimIdentToB); const claim = claimsUtils.decodeClaim(claimEncoded); await testProvider.publishClaim(identityId, claim); - }, global.polykeyStartupTimeout * 2); + }, global.defaultTimeout * 3); afterAll(async () => { await nodeC.stop(); await nodeB.stop(); @@ -723,6 +770,9 @@ describe('CLI Identities', () => { recursive: true, }); }); + beforeEach(async () => { + await polykeyAgent.gestaltGraph.clearDB(); + }); afterEach(async () => { // Clean the local nodes gestalt graph here. await polykeyAgent.gestaltGraph.clearDB(); @@ -732,8 +782,8 @@ describe('CLI Identities', () => { test('Should start discovery by Node', async () => { // Authenticate identity await polykeyAgent.identitiesManager.putToken( - testToken.providerId, - identityId, + testProvider.id, + testToken.identityId, testToken.tokenData, ); @@ -747,10 +797,28 @@ describe('CLI Identities', () => { ]; const result = await testBinUtils.pkStdio(commands); expect(result.exitCode).toBe(0); - - // We expect to find a gestalt now. - const gestalt = await polykeyAgent.gestaltGraph.getGestalts(); - expect(gestalt.length).not.toBe(0); + // Should eventually discover entire gestalt + const gestalt = await poll( + async () => { + const gestalts = await poll>( + async () => { + return await polykeyAgent.gestaltGraph.getGestalts(); + }, + (_, result) => { + if (result.length === 1) return true; + return false; + }, + 100, + ); + return gestalts[0]; + }, + (_, result) => { + if (result === undefined) return false; + if (Object.keys(result.matrix).length === 3) return true; + return false; + }, + 100, + ); const gestaltString = JSON.stringify(gestalt); expect(gestaltString).toContain( nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), @@ -761,15 +829,15 @@ describe('CLI Identities', () => { expect(gestaltString).toContain(identityId); // Unauthenticate identity await polykeyAgent.identitiesManager.delToken( - testToken.providerId, - identityId, + testProvider.id, + testToken.identityId, ); }); test('Should start discovery by Identity', async () => { // Authenticate identity await polykeyAgent.identitiesManager.putToken( - testToken.providerId, - identityId, + testProvider.id, + testToken.identityId, testToken.tokenData, ); const commands = [ @@ -781,9 +849,28 @@ describe('CLI Identities', () => { ]; const result = await testBinUtils.pkStdio(commands, {}, dataDir); expect(result.exitCode).toBe(0); - // We expect to find a gestalt now. - const gestalt = await polykeyAgent.gestaltGraph.getGestalts(); - expect(gestalt.length).not.toBe(0); + // Should eventually discover entire gestalt + const gestalt = await poll( + async () => { + const gestalts = await poll>( + async () => { + return await polykeyAgent.gestaltGraph.getGestalts(); + }, + (_, result) => { + if (result.length === 1) return true; + return false; + }, + 100, + ); + return gestalts[0]; + }, + (_, result) => { + if (result === undefined) return false; + if (Object.keys(result.matrix).length === 3) return true; + return false; + }, + 100, + ); const gestaltString = JSON.stringify(gestalt); expect(gestaltString).toContain( nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), @@ -794,8 +881,8 @@ describe('CLI Identities', () => { expect(gestaltString).toContain(identityId); // Unauthenticate identity await polykeyAgent.identitiesManager.delToken( - testToken.providerId, - identityId, + testProvider.id, + testToken.identityId, ); }); }); diff --git a/tests/client/rpcGestalts.test.ts b/tests/client/rpcGestalts.test.ts index 2e4ff9d51..bd9fde312 100644 --- a/tests/client/rpcGestalts.test.ts +++ b/tests/client/rpcGestalts.test.ts @@ -3,22 +3,23 @@ import type { IdentitiesManager } from '@/identities'; import type { GestaltGraph } from '@/gestalts'; import type { IdentityId, IdentityInfo, ProviderId } from '@/identities/types'; import type { NodeIdEncoded, NodeInfo } from '@/nodes/types'; -import type * as gestaltsPB from '@/proto/js/polykey/v1/gestalts/gestalts_pb'; +import type { Discovery } from '@/discovery'; import type { ClientServiceClient } from '@/proto/js/polykey/v1/client_service_grpc_pb'; +import type * as gestaltsPB from '@/proto/js/polykey/v1/gestalts/gestalts_pb'; import os from 'os'; import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { PolykeyAgent } from '@'; +import { NodeManager } from '@/nodes'; +import { KeyManager } from '@/keys'; +import { ForwardProxy } from '@/network'; import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; import * as permissionsPB from '@/proto/js/polykey/v1/permissions/permissions_pb'; -import { KeyManager } from '@/keys'; -import { ForwardProxy } from '@/network'; import * as grpcUtils from '@/grpc/utils'; import * as gestaltsUtils from '@/gestalts/utils'; -import * as nodesErrors from '@/nodes/errors'; import * as nodesUtils from '@/nodes/utils'; import * as testUtils from './utils'; import TestProvider from '../identities/TestProvider'; @@ -42,6 +43,7 @@ describe('Client service', () => { let keyManager: KeyManager; let gestaltGraph: GestaltGraph; let identitiesManager: IdentitiesManager; + let discovery: Discovery; let passwordFile: string; let callCredentials: grpc.Metadata; @@ -104,6 +106,7 @@ describe('Client service', () => { gestaltGraph = pkAgent.gestaltGraph; identitiesManager = pkAgent.identitiesManager; + discovery = pkAgent.discovery; // Adding provider const testProvider = new TestProvider(); @@ -242,6 +245,10 @@ describe('Client service', () => { expect(jsonString).toContain(nodesUtils.encodeNodeId(nodeId2)); // Contains NodeId }); test('should discover gestalt via Node.', async () => { + const mockedRequestChainData = jest + .spyOn(NodeManager.prototype, 'requestChainData') + .mockResolvedValue({}); + const gestaltsDiscoverNode = grpcUtils.promisifyUnaryCall( client, @@ -250,12 +257,20 @@ describe('Client service', () => { const nodeMessage = new nodesPB.Node(); nodeMessage.setNodeId(nodesUtils.encodeNodeId(nodeId2)); - // I have no idea how to test this. so we just check for expected error for now - await expect(() => - gestaltsDiscoverNode(nodeMessage, callCredentials), - ).rejects.toThrow(nodesErrors.ErrorNodeGraphEmptyDatabase); + expect( + await gestaltsDiscoverNode(nodeMessage, callCredentials), + ).toBeInstanceOf(utilsPB.EmptyMessage); + + // Revert side-effects + await discovery.stop(); + await discovery.start({ fresh: true }); + mockedRequestChainData.mockRestore(); }); test('should discover gestalt via Identity.', async () => { + const mockedRequestChainData = jest + .spyOn(NodeManager.prototype, 'requestChainData') + .mockResolvedValue({}); + const gestaltsDiscoverIdentity = grpcUtils.promisifyUnaryCall( client, @@ -271,10 +286,14 @@ describe('Client service', () => { const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(testToken.providerId); providerMessage.setIdentityId(testToken.identityId); - // Technically contains a node, but no other thing, will succeed with no results expect( await gestaltsDiscoverIdentity(providerMessage, callCredentials), ).toBeInstanceOf(utilsPB.EmptyMessage); + + // Revert side-effects + await discovery.stop(); + await discovery.start({ fresh: true }); + mockedRequestChainData.mockRestore(); }); test('should get gestalt permissions by node.', async () => { const gestaltsGetActionsByNode = diff --git a/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts b/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts new file mode 100644 index 000000000..b28a46770 --- /dev/null +++ b/tests/client/service/gestaltsGestaltTrustByIdentity.test.ts @@ -0,0 +1,470 @@ +import type { Host, Port } from '@/network/types'; +import type { NodeIdEncoded } from '@/nodes/types'; +import type { IdentityId } from '@/identities/types'; +import type { ClaimLinkIdentity } from '@/claims/types'; +import type { ChainData } from '@/sigchain/types'; +import type { Gestalt } from '@/gestalts/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { Metadata } from '@grpc/grpc-js'; +import { DB } from '@matrixai/db'; +import { PolykeyAgent } from '@'; +import { KeyManager } from '@/keys'; +import { GestaltGraph } from '@/gestalts'; +import { ACL } from '@/acl'; +import { GRPCServer } from '@/grpc'; +import { Discovery } from '@/discovery'; +import { IdentitiesManager } from '@/identities'; +import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; +import { Sigchain } from '@/sigchain'; +import { ForwardProxy, ReverseProxy } from '@/network'; +import { GRPCClientClient, ClientServiceService } from '@/client'; +import gestaltsGestaltTrustByIdentity from '@/client/service/gestaltsGestaltTrustByIdentity'; +import { poll } from '@/utils'; +import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as nodesUtils from '@/nodes/utils'; +import * as claimsUtils from '@/claims/utils'; +import * as gestaltsErrors from '@/gestalts/errors'; +import * as clientUtils from '@/client/utils'; +import * as keysUtils from '@/keys/utils'; +import * as testUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('gestaltsGestaltTrustByIdentity', () => { + const logger = new Logger( + 'gestaltsGestaltTrustByIdentity test', + LogLevel.WARN, + [new StreamHandler()], + ); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + const testProvider = new TestProvider(); + // Create node to trust + const connectedIdentity = 'trusted-node' as IdentityId; + let nodeDataDir: string; + let node: PolykeyAgent; + let nodeId: NodeIdEncoded; + const nodeChainData: ChainData = {}; + let mockedRequestChainData: jest.SpyInstance; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + const nodeKeyPair = await keysUtils.generateKeyPair(2048); + mockedRequestChainData = jest + .spyOn(NodeManager.prototype, 'requestChainData') + .mockResolvedValue(nodeChainData); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(nodeKeyPair) + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(nodeKeyPair) + .mockResolvedValue(globalKeyPair); + nodeDataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'trusted-node-'), + ); + const nodePath = path.join(nodeDataDir, 'polykey'); + node = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + logger, + }); + nodeId = nodesUtils.encodeNodeId(node.keyManager.getNodeId()); + node.identitiesManager.registerProvider(testProvider); + await node.identitiesManager.putToken(testProvider.id, connectedIdentity, { + accessToken: 'abc123', + }); + testProvider.users['trusted-node'] = {}; + const identityClaim: ClaimLinkIdentity = { + type: 'identity', + node: nodesUtils.encodeNodeId(node.keyManager.getNodeId()), + provider: testProvider.id, + identity: connectedIdentity, + }; + const [claimId, claimEncoded] = await node.sigchain.addClaim(identityClaim); + const claim = claimsUtils.decodeClaim(claimEncoded); + nodeChainData[claimId] = claim; + await testProvider.publishClaim(connectedIdentity, claim); + }, global.maxTimeout); + afterAll(async () => { + await node.stop(); + await fs.promises.rm(nodeDataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + mockedRequestChainData.mockRestore(); + }); + const authToken = 'abc123'; + let dataDir: string; + let discovery: Discovery; + let gestaltGraph: GestaltGraph; + let identitiesManager: IdentitiesManager; + let nodeManager: NodeManager; + let nodeConnectionManager: NodeConnectionManager; + let nodeGraph: NodeGraph; + let sigchain: Sigchain; + let fwdProxy: ForwardProxy; + let revProxy: ReverseProxy; + let acl: ACL; + let db: DB; + let keyManager: KeyManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + crypto: { + key: keyManager.dbKey, + ops: { + encrypt: keysUtils.encryptWithKey, + decrypt: keysUtils.decryptWithKey, + }, + }, + }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + logger, + }); + identitiesManager.registerProvider(testProvider); + await identitiesManager.putToken( + testProvider.id, + 'test-user' as IdentityId, + { + accessToken: 'def456', + }, + ); + fwdProxy = new ForwardProxy({ + authToken, + logger, + }); + await fwdProxy.start({ + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, + }); + revProxy = new ReverseProxy({ logger }); + await revProxy.start({ + serverHost: '1.1.1.1' as Host, + serverPort: 1 as Port, + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyManager, + logger, + }); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger: logger.getChild('NodeGraph'), + }); + nodeConnectionManager = new NodeConnectionManager({ + keyManager, + nodeGraph, + fwdProxy, + revProxy, + connConnectTime: 2000, + connTimeoutTime: 2000, + logger: logger.getChild('NodeConnectionManager'), + }); + await nodeConnectionManager.start(); + nodeManager = new NodeManager({ + db, + keyManager, + sigchain, + nodeGraph, + nodeConnectionManager, + logger: logger.getChild('nodeManager'), + }); + await nodeManager.setNode(nodesUtils.decodeNodeId(nodeId)!, { + host: node.revProxy.getIngressHost(), + port: node.revProxy.getIngressPort(), + }); + discovery = await Discovery.createDiscovery({ + db, + keyManager, + gestaltGraph, + identitiesManager, + nodeManager, + sigchain, + logger, + }); + const clientService = { + gestaltsGestaltTrustByIdentity: gestaltsGestaltTrustByIdentity({ + authenticate, + gestaltGraph, + discovery, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: keyManager.getNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.port, + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await discovery.stop(); + await nodeConnectionManager.stop(); + await nodeGraph.stop(); + await revProxy.stop(); + await fwdProxy.stop(); + await sigchain.stop(); + await identitiesManager.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('trusts an identity (already set in gestalt graph)', async () => { + testProvider.users['disconnected-user'] = {}; + await gestaltGraph.linkNodeAndIdentity( + { + id: nodeId, + chain: {}, + }, + { + providerId: testProvider.id, + identityId: connectedIdentity, + claims: {}, + }, + ); + const request = new identitiesPB.Provider(); + request.setProviderId(testProvider.id); + request.setIdentityId(connectedIdentity); + const response = await grpcClient.gestaltsGestaltTrustByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + expect( + await gestaltGraph.getGestaltActionsByIdentity( + testProvider.id, + connectedIdentity, + ), + ).toEqual({ + notify: null, + }); + // Reverse side effects + await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); + await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); + }); + test('trusts an identity (new identity)', async () => { + const request = new identitiesPB.Provider(); + request.setProviderId(testProvider.id); + request.setIdentityId(connectedIdentity); + // Should fail on first attempt - need to allow time for the identity to be + // linked to a node via discovery + await expect( + grpcClient.gestaltsGestaltTrustByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ), + ).rejects.toThrow(gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing); + // Wait for both identity and node to be set in GG + await poll( + async () => { + const gestalts = await poll>( + async () => { + return await gestaltGraph.getGestalts(); + }, + (_, result) => { + if (result.length === 1) return true; + return false; + }, + 100, + ); + return gestalts[0]; + }, + (_, result) => { + if (result === undefined) return false; + if (Object.keys(result.matrix).length === 2) return true; + return false; + }, + 100, + ); + const response = await grpcClient.gestaltsGestaltTrustByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + expect( + await gestaltGraph.getGestaltActionsByIdentity( + testProvider.id, + connectedIdentity, + ), + ).toEqual({ + notify: null, + }); + // Reverse side effects + await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); + await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); + }); + test('cannot trust a disconnected identity', async () => { + testProvider.users['disconnected-user'] = {}; + const request = new identitiesPB.Provider(); + request.setProviderId(testProvider.id); + request.setIdentityId('disconnected-user'); + // Should fail on first attempt - attempt to find a connected node + await expect( + grpcClient.gestaltsGestaltTrustByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ), + ).rejects.toThrow(gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing); + // Wait and try again - should fail again because the identity has no + // linked nodes we can trust + await expect( + grpcClient.gestaltsGestaltTrustByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ), + ).rejects.toThrow(gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing); + }); + test('trust extends to entire gestalt', async () => { + await gestaltGraph.linkNodeAndIdentity( + { + id: nodeId, + chain: {}, + }, + { + providerId: testProvider.id, + identityId: connectedIdentity, + claims: {}, + }, + ); + const request = new identitiesPB.Provider(); + request.setProviderId(testProvider.id); + request.setIdentityId(connectedIdentity); + const response = await grpcClient.gestaltsGestaltTrustByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + expect( + await gestaltGraph.getGestaltActionsByIdentity( + testProvider.id, + connectedIdentity, + ), + ).toEqual({ + notify: null, + }); + expect( + await gestaltGraph.getGestaltActionsByNode( + nodesUtils.decodeNodeId(nodeId)!, + ), + ).toEqual({ + notify: null, + }); + // Reverse side effects + await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); + await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); + }); + test('links trusted identity to an existing node', async () => { + await gestaltGraph.setNode({ + id: nodeId, + chain: {}, + }); + const request = new identitiesPB.Provider(); + request.setProviderId(testProvider.id); + request.setIdentityId(connectedIdentity); + // Should fail on first attempt - need to allow time for the identity to be + // linked to a node via discovery + await expect( + grpcClient.gestaltsGestaltTrustByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ), + ).rejects.toThrow(gestaltsErrors.ErrorGestaltsGraphIdentityIdMissing); + // Wait and try again - should succeed second time + // Wait for both identity and node to be set in GG + await poll( + async () => { + const gestalts = await poll>( + async () => { + return await gestaltGraph.getGestalts(); + }, + (_, result) => { + if (result.length === 1) return true; + return false; + }, + 100, + ); + return gestalts[0]; + }, + (_, result) => { + if (result === undefined) return false; + if (Object.keys(result.matrix).length === 2) return true; + return false; + }, + 100, + ); + const response = await grpcClient.gestaltsGestaltTrustByIdentity( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + expect( + await gestaltGraph.getGestaltActionsByIdentity( + testProvider.id, + connectedIdentity, + ), + ).toEqual({ + notify: null, + }); + expect( + await gestaltGraph.getGestaltActionsByNode( + nodesUtils.decodeNodeId(nodeId)!, + ), + ).toEqual({ + notify: null, + }); + // Reverse side effects + await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); + await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); + }); +}); diff --git a/tests/client/service/gestaltsGestaltTrustByNode.test.ts b/tests/client/service/gestaltsGestaltTrustByNode.test.ts new file mode 100644 index 000000000..0b37758be --- /dev/null +++ b/tests/client/service/gestaltsGestaltTrustByNode.test.ts @@ -0,0 +1,352 @@ +import type { Host, Port } from '@/network/types'; +import type { NodeIdEncoded } from '@/nodes/types'; +import type { IdentityId } from '@/identities/types'; +import type { ClaimLinkIdentity } from '@/claims/types'; +import type { ChainData } from '@/sigchain/types'; +import type { Gestalt } from '@/gestalts/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { Metadata } from '@grpc/grpc-js'; +import { DB } from '@matrixai/db'; +import { PolykeyAgent } from '@'; +import { KeyManager } from '@/keys'; +import { GestaltGraph } from '@/gestalts'; +import { ACL } from '@/acl'; +import { GRPCServer } from '@/grpc'; +import { Discovery } from '@/discovery'; +import { IdentitiesManager } from '@/identities'; +import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; +import { Sigchain } from '@/sigchain'; +import { ForwardProxy, ReverseProxy } from '@/network'; +import { GRPCClientClient, ClientServiceService } from '@/client'; +import gestaltsGestaltTrustByNode from '@/client/service/gestaltsGestaltTrustByNode'; +import { poll } from '@/utils'; +import * as nodesPB from '@/proto/js/polykey/v1/nodes/nodes_pb'; +import * as utilsPB from '@/proto/js/polykey/v1/utils/utils_pb'; +import * as nodesUtils from '@/nodes/utils'; +import * as claimsUtils from '@/claims/utils'; +import * as keysUtils from '@/keys/utils'; +import * as clientUtils from '@/client/utils'; +import * as testUtils from '../../utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('gestaltsGestaltTrustByNode', () => { + const logger = new Logger('gestaltsGestaltTrustByNode test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + const testProvider = new TestProvider(); + // Create node to trust + const connectedIdentity = 'trusted-node' as IdentityId; + let nodeDataDir: string; + let node: PolykeyAgent; + let nodeId: NodeIdEncoded; + const nodeChainData: ChainData = {}; + let mockedRequestChainData: jest.SpyInstance; + let mockedGenerateKeyPair: jest.SpyInstance; + let mockedGenerateDeterministicKeyPair: jest.SpyInstance; + beforeAll(async () => { + const globalKeyPair = await testUtils.setupGlobalKeypair(); + const nodeKeyPair = await keysUtils.generateKeyPair(2048); + mockedRequestChainData = jest + .spyOn(NodeManager.prototype, 'requestChainData') + .mockResolvedValue(nodeChainData); + mockedGenerateKeyPair = jest + .spyOn(keysUtils, 'generateKeyPair') + .mockResolvedValueOnce(nodeKeyPair) + .mockResolvedValue(globalKeyPair); + mockedGenerateDeterministicKeyPair = jest + .spyOn(keysUtils, 'generateDeterministicKeyPair') + .mockResolvedValueOnce(nodeKeyPair) + .mockResolvedValue(globalKeyPair); + nodeDataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'trusted-node-'), + ); + const nodePath = path.join(nodeDataDir, 'polykey'); + node = await PolykeyAgent.createPolykeyAgent({ + password, + nodePath, + logger, + }); + nodeId = nodesUtils.encodeNodeId(node.keyManager.getNodeId()); + node.identitiesManager.registerProvider(testProvider); + await node.identitiesManager.putToken(testProvider.id, connectedIdentity, { + accessToken: 'abc123', + }); + testProvider.users['trusted-node'] = {}; + const identityClaim: ClaimLinkIdentity = { + type: 'identity', + node: nodesUtils.encodeNodeId(node.keyManager.getNodeId()), + provider: testProvider.id, + identity: connectedIdentity, + }; + const [claimId, claimEncoded] = await node.sigchain.addClaim(identityClaim); + const claim = claimsUtils.decodeClaim(claimEncoded); + nodeChainData[claimId] = claim; + await testProvider.publishClaim(connectedIdentity, claim); + }, global.maxTimeout); + afterAll(async () => { + await node.stop(); + await fs.promises.rm(nodeDataDir, { + force: true, + recursive: true, + }); + mockedGenerateKeyPair.mockRestore(); + mockedGenerateDeterministicKeyPair.mockRestore(); + mockedRequestChainData.mockRestore(); + }); + const authToken = 'abc123'; + let dataDir: string; + let discovery: Discovery; + let gestaltGraph: GestaltGraph; + let identitiesManager: IdentitiesManager; + let nodeManager: NodeManager; + let nodeConnectionManager: NodeConnectionManager; + let nodeGraph: NodeGraph; + let sigchain: Sigchain; + let fwdProxy: ForwardProxy; + let revProxy: ReverseProxy; + let acl: ACL; + let db: DB; + let keyManager: KeyManager; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const keysPath = path.join(dataDir, 'keys'); + keyManager = await KeyManager.createKeyManager({ + password, + keysPath, + logger, + }); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + crypto: { + key: keyManager.dbKey, + ops: { + encrypt: keysUtils.encryptWithKey, + decrypt: keysUtils.decryptWithKey, + }, + }, + }); + acl = await ACL.createACL({ + db, + logger, + }); + gestaltGraph = await GestaltGraph.createGestaltGraph({ + db, + acl, + logger, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + logger, + }); + identitiesManager.registerProvider(testProvider); + await identitiesManager.putToken( + testProvider.id, + 'test-user' as IdentityId, + { + accessToken: 'def456', + }, + ); + fwdProxy = new ForwardProxy({ + authToken, + logger, + }); + await fwdProxy.start({ + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, + }); + revProxy = new ReverseProxy({ logger }); + await revProxy.start({ + serverHost: '1.1.1.1' as Host, + serverPort: 1 as Port, + tlsConfig: { + keyPrivatePem: keyManager.getRootKeyPairPem().privateKey, + certChainPem: await keyManager.getRootCertChainPem(), + }, + }); + sigchain = await Sigchain.createSigchain({ + db, + keyManager, + logger, + }); + nodeGraph = await NodeGraph.createNodeGraph({ + db, + keyManager, + logger: logger.getChild('NodeGraph'), + }); + nodeConnectionManager = new NodeConnectionManager({ + keyManager, + nodeGraph, + fwdProxy, + revProxy, + connConnectTime: 2000, + connTimeoutTime: 2000, + logger: logger.getChild('NodeConnectionManager'), + }); + await nodeConnectionManager.start(); + nodeManager = new NodeManager({ + db, + keyManager, + sigchain, + nodeGraph, + nodeConnectionManager, + logger: logger.getChild('nodeManager'), + }); + await nodeManager.setNode(nodesUtils.decodeNodeId(nodeId)!, { + host: node.revProxy.getIngressHost(), + port: node.revProxy.getIngressPort(), + }); + discovery = await Discovery.createDiscovery({ + db, + keyManager, + gestaltGraph, + identitiesManager, + nodeManager, + sigchain, + logger, + }); + const clientService = { + gestaltsGestaltTrustByNode: gestaltsGestaltTrustByNode({ + authenticate, + gestaltGraph, + discovery, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: keyManager.getNodeId(), + host: '127.0.0.1' as Host, + port: grpcServer.port, + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await discovery.stop(); + await nodeConnectionManager.stop(); + await nodeGraph.stop(); + await revProxy.stop(); + await fwdProxy.stop(); + await sigchain.stop(); + await identitiesManager.stop(); + await gestaltGraph.stop(); + await acl.stop(); + await db.stop(); + await keyManager.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('trusts a node (already set in gestalt graph)', async () => { + await gestaltGraph.setNode({ + id: nodeId, + chain: {}, + }); + const request = new nodesPB.Node(); + request.setNodeId(nodeId); + const response = await grpcClient.gestaltsGestaltTrustByNode( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + expect( + await gestaltGraph.getGestaltActionsByNode( + nodesUtils.decodeNodeId(nodeId)!, + ), + ).toEqual({ + notify: null, + }); + // Reverse side effects + await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); + await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); + }); + test('trusts a node (new node)', async () => { + const request = new nodesPB.Node(); + request.setNodeId(nodeId); + const response = await grpcClient.gestaltsGestaltTrustByNode( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + expect( + await gestaltGraph.getGestaltActionsByNode( + nodesUtils.decodeNodeId(nodeId)!, + ), + ).toEqual({ + notify: null, + }); + // Reverse side effects + await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); + await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); + }); + test('trust extends to entire gestalt', async () => { + const request = new nodesPB.Node(); + request.setNodeId(nodeId); + const response = await grpcClient.gestaltsGestaltTrustByNode( + request, + clientUtils.encodeAuthFromPassword(password), + ); + expect(response).toBeInstanceOf(utilsPB.EmptyMessage); + expect( + await gestaltGraph.getGestaltActionsByNode( + nodesUtils.decodeNodeId(nodeId)!, + ), + ).toEqual({ + notify: null, + }); + // Give discovery process time to complete before checking identity actions + // Wait for both identity and node to be set in GG + await poll( + async () => { + const gestalts = await poll>( + async () => { + return await gestaltGraph.getGestalts(); + }, + (_, result) => { + if (result.length === 1) return true; + return false; + }, + 100, + ); + return gestalts[0]; + }, + (_, result) => { + if (result === undefined) return false; + if (Object.keys(result.matrix).length === 2) return true; + return false; + }, + 100, + ); + expect( + await gestaltGraph.getGestaltActionsByIdentity( + testProvider.id, + connectedIdentity, + ), + ).toEqual({ + notify: null, + }); + // Reverse side effects + await gestaltGraph.unsetNode(nodesUtils.decodeNodeId(nodeId)!); + await gestaltGraph.unsetIdentity(testProvider.id, connectedIdentity); + }); +}); diff --git a/tests/client/service/identitiesAuthenticatedGet.test.ts b/tests/client/service/identitiesAuthenticatedGet.test.ts new file mode 100644 index 000000000..8e650ca13 --- /dev/null +++ b/tests/client/service/identitiesAuthenticatedGet.test.ts @@ -0,0 +1,236 @@ +import type { IdentityId, ProviderId } from '@/identities/types'; +import type { Host, Port } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { Metadata } from '@grpc/grpc-js'; +import { DB } from '@matrixai/db'; +import { GRPCServer } from '@/grpc'; +import { IdentitiesManager } from '@/identities'; +import { GRPCClientClient, ClientServiceService } from '@/client'; +import identitiesAuthenticatedGet from '@/client/service/identitiesAuthenticatedGet'; +import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import * as nodesUtils from '@/nodes/utils'; +import * as clientUtils from '@/client/utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('identitiesAuthenticatedGet', () => { + const logger = new Logger('identitiesAuthenticatedGet test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + const tokenData = { + accessToken: 'abc123', + }; + let dataDir: string; + let identitiesManager: IdentitiesManager; + let db: DB; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + logger, + }); + const clientService = { + identitiesAuthenticatedGet: identitiesAuthenticatedGet({ + authenticate, + identitiesManager, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: nodesUtils.decodeNodeId( + 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', + )!, + host: '127.0.0.1' as Host, + port: grpcServer.port, + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await identitiesManager.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets an authenticated identity', async () => { + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + }; + provider.users['user1'] = user1; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + user1.providerId, + user1.identityId, + tokenData, + ); + const request = new identitiesPB.OptionalProvider(); + const response = grpcClient.identitiesAuthenticatedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const providerMessage of response) { + expect(providerMessage).toBeInstanceOf(identitiesPB.Provider); + const obj = providerMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual(user1); + }); + test('does not get an unauthenticated identity', async () => { + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + }; + provider.users['user1'] = user1; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + user1.providerId, + user1.identityId, + tokenData, + ); + await identitiesManager.delToken(user1.providerId, user1.identityId); + const request = new identitiesPB.OptionalProvider(); + const response = grpcClient.identitiesAuthenticatedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const providerMessage of response) { + expect(providerMessage).toBeInstanceOf(identitiesPB.Provider); + const obj = providerMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(0); + }); + test('gets authenticated identities from multiple providers', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + }; + const user2 = { + providerId: provider1.id, + identityId: 'user2' as IdentityId, + }; + const user3 = { + providerId: provider2.id, + identityId: 'user3' as IdentityId, + }; + provider1.users['user1'] = user1; + provider1.users['user2'] = user2; + provider2.users['user3'] = user3; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + user1.providerId, + user1.identityId, + tokenData, + ); + await identitiesManager.putToken( + user2.providerId, + user2.identityId, + tokenData, + ); + await identitiesManager.putToken( + user3.providerId, + user3.identityId, + tokenData, + ); + const request = new identitiesPB.OptionalProvider(); + const response = grpcClient.identitiesAuthenticatedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const providerMessage of response) { + expect(providerMessage).toBeInstanceOf(identitiesPB.Provider); + const obj = providerMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(3); + expect(output[0]).toEqual(user1); + expect(output[1]).toEqual(user2); + expect(output[2]).toEqual(user3); + }); + test('gets authenticated identities a specific provider', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + }; + const user2 = { + providerId: provider1.id, + identityId: 'user2' as IdentityId, + }; + const user3 = { + providerId: provider2.id, + identityId: 'user3' as IdentityId, + }; + provider1.users['user1'] = user1; + provider1.users['user2'] = user2; + provider2.users['user3'] = user3; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + user1.providerId, + user1.identityId, + tokenData, + ); + await identitiesManager.putToken( + user2.providerId, + user2.identityId, + tokenData, + ); + await identitiesManager.putToken( + user3.providerId, + user3.identityId, + tokenData, + ); + const request = new identitiesPB.OptionalProvider(); + request.setProviderId(provider2.id); + const response = grpcClient.identitiesAuthenticatedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const providerMessage of response) { + expect(providerMessage).toBeInstanceOf(identitiesPB.Provider); + const obj = providerMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual(user3); + }); +}); diff --git a/tests/client/service/identitiesInfoConnectedGet.test.ts b/tests/client/service/identitiesInfoConnectedGet.test.ts new file mode 100644 index 000000000..bd7372a43 --- /dev/null +++ b/tests/client/service/identitiesInfoConnectedGet.test.ts @@ -0,0 +1,739 @@ +import type { IdentityId, ProviderId } from '@/identities/types'; +import type { Host, Port } from '@/network/types'; +import fs from 'fs'; +import path from 'path'; +import os from 'os'; +import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; +import { Metadata } from '@grpc/grpc-js'; +import { DB } from '@matrixai/db'; +import { GRPCServer } from '@/grpc'; +import { IdentitiesManager } from '@/identities'; +import { GRPCClientClient, ClientServiceService } from '@/client'; +import identitiesInfoConnectedGet from '@/client/service/identitiesInfoConnectedGet'; +import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; +import * as nodesUtils from '@/nodes/utils'; +import * as identitiesErrors from '@/identities/errors'; +import * as clientUtils from '@/client/utils'; +import TestProvider from '../../identities/TestProvider'; + +describe('identitiesInfoConnectedGet', () => { + const logger = new Logger('identitiesInfoConnectedGet test', LogLevel.WARN, [ + new StreamHandler(), + ]); + const password = 'helloworld'; + const authenticate = async (metaClient, metaServer = new Metadata()) => + metaServer; + const testToken = { + identityId: 'test_user' as IdentityId, + tokenData: { + accessToken: 'abc123', + }, + }; + let dataDir: string; + let identitiesManager: IdentitiesManager; + let db: DB; + let grpcServer: GRPCServer; + let grpcClient: GRPCClientClient; + beforeEach(async () => { + dataDir = await fs.promises.mkdtemp( + path.join(os.tmpdir(), 'polykey-test-'), + ); + const dbPath = path.join(dataDir, 'db'); + db = await DB.createDB({ + dbPath, + logger, + }); + identitiesManager = await IdentitiesManager.createIdentitiesManager({ + db, + logger, + }); + const clientService = { + identitiesInfoConnectedGet: identitiesInfoConnectedGet({ + authenticate, + identitiesManager, + }), + }; + grpcServer = new GRPCServer({ logger }); + await grpcServer.start({ + services: [[ClientServiceService, clientService]], + host: '127.0.0.1' as Host, + port: 0 as Port, + }); + grpcClient = await GRPCClientClient.createGRPCClientClient({ + nodeId: nodesUtils.decodeNodeId( + 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', + )!, + host: '127.0.0.1' as Host, + port: grpcServer.port, + logger, + }); + }); + afterEach(async () => { + await grpcClient.destroy(); + await grpcServer.stop(); + await identitiesManager.stop(); + await db.stop(); + await fs.promises.rm(dataDir, { + force: true, + recursive: true, + }); + }); + test('gets connected identities from a single provider', async () => { + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.tokenData, + ); + provider.users[testToken.identityId].connected = [ + user1.identityId, + user2.identityId, + ]; + const request = new identitiesPB.ProviderSearch(); + request.setProviderIdList([provider.id]); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + provider: { + identityId: user2.identityId, + providerId: user2.providerId, + }, + url: user2.url, + }); + }); + test('gets connected identities to a particular identity id', async () => { + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.tokenData, + ); + provider.users[testToken.identityId].connected = [user1.identityId]; + await identitiesManager.putToken( + provider.id, + 'otherAuthenticatedId' as IdentityId, + testToken.tokenData, + ); + provider.users['otherAuthenticatedId'] = { connected: [user2.identityId] }; + const request = new identitiesPB.ProviderSearch(); + request.setProviderIdList([provider.id]); + request.setAuthIdentityId('otherAuthenticatedId'); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user2.email, + name: user2.name, + provider: { + identityId: user2.identityId, + providerId: user2.providerId, + }, + url: user2.url, + }); + }); + test('gets connected identities from multiple providers', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user2'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.tokenData, + ); + provider2.users[testToken.identityId].connected = [user2.identityId]; + const request = new identitiesPB.ProviderSearch(); + request.setProviderIdList([provider1.id, provider2.id]); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + provider: { + identityId: user2.identityId, + providerId: user2.providerId, + }, + url: user2.url, + }); + }); + test('gets connected identities from all providers', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user2'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.tokenData, + ); + provider2.users[testToken.identityId].connected = [user2.identityId]; + const request = new identitiesPB.ProviderSearch(); + request.setProviderIdList([]); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + provider: { + identityId: user2.identityId, + providerId: user2.providerId, + }, + url: user2.url, + }); + }); + test('searches for identities matching a search term', async () => { + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.tokenData, + ); + provider.users[testToken.identityId].connected = [ + user1.identityId, + user2.identityId, + ]; + const request = new identitiesPB.ProviderSearch(); + request.setSearchTermList(['1']); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + }); + test('searches for identities matching multiple search terms', async () => { + // Setup providers + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.tokenData, + ); + provider.users[testToken.identityId].connected = [ + user1.identityId, + user2.identityId, + ]; + const request = new identitiesPB.ProviderSearch(); + request.setSearchTermList(['1', '2']); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + provider: { + identityId: user2.identityId, + providerId: user2.providerId, + }, + url: user2.url, + }); + }); + test('searches for identities matching a search term across multiple providers', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.tokenData, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + provider2.users[testToken.identityId].connected = [user2.identityId]; + const request = new identitiesPB.ProviderSearch(); + request.setSearchTermList(['1']); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + provider: { + identityId: user2.identityId, + providerId: user2.providerId, + }, + url: user2.url, + }); + }); + test('gets no connected identities', async () => { + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.tokenData, + ); + provider.users[testToken.identityId].connected = [ + user1.identityId, + user2.identityId, + ]; + const request = new identitiesPB.ProviderSearch(); + request.setLimit('0'); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(0); + }); + test('gets one connected identity', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user2'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.tokenData, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + provider2.users[testToken.identityId].connected = [user2.identityId]; + const request = new identitiesPB.ProviderSearch(); + request.setLimit('1'); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + }); + test('cannot get more identities than available', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user2'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.tokenData, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + provider2.users[testToken.identityId].connected = [user2.identityId]; + const request = new identitiesPB.ProviderSearch(); + request.setLimit('3'); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + provider: { + identityId: user2.identityId, + providerId: user2.providerId, + }, + url: user2.url, + }); + }); + test('can only get from authenticated providers', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user2'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + provider1.users[testToken.identityId].connected = [user1.identityId]; + provider2.users[testToken.identityId].connected = [user2.identityId]; + const request = new identitiesPB.ProviderSearch(); + const response = grpcClient.identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + }); + test('gets disconnected identities', async () => { + // This feature is not implemented yet - should throw error + const request = new identitiesPB.ProviderSearch(); + request.setDisconnected(true); + await expect( + grpcClient + .identitiesInfoConnectedGet( + request, + clientUtils.encodeAuthFromPassword(password), + ) + .next(), + ).rejects.toThrow(identitiesErrors.ErrorProviderUnimplemented); + }); +}); diff --git a/tests/client/service/identitiesInfoGet.test.ts b/tests/client/service/identitiesInfoGet.test.ts index 344444210..a6eaaef7a 100644 --- a/tests/client/service/identitiesInfoGet.test.ts +++ b/tests/client/service/identitiesInfoGet.test.ts @@ -8,14 +8,11 @@ import { Metadata } from '@grpc/grpc-js'; import { DB } from '@matrixai/db'; import { GRPCServer } from '@/grpc'; import { IdentitiesManager } from '@/identities'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; +import { GRPCClientClient, ClientServiceService } from '@/client'; import identitiesInfoGet from '@/client/service/identitiesInfoGet'; import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; -import { utils as nodesUtils } from '@/nodes'; +import * as nodesUtils from '@/nodes/utils'; +import * as clientUtils from '@/client/utils'; import TestProvider from '../../identities/TestProvider'; describe('identitiesInfoGet', () => { @@ -26,23 +23,12 @@ describe('identitiesInfoGet', () => { const authenticate = async (metaClient, metaServer = new Metadata()) => metaServer; const testToken = { - providerId: 'test-provider' as ProviderId, identityId: 'test_user' as IdentityId, tokenData: { accessToken: 'abc123', }, }; - let mockedGetAuthIdentityIds: jest.SpyInstance; - beforeAll(async () => { - mockedGetAuthIdentityIds = jest - .spyOn(TestProvider.prototype, 'getAuthIdentityIds') - .mockResolvedValue([testToken.identityId]); - }); - afterAll(async () => { - mockedGetAuthIdentityIds.mockRestore(); - }); let dataDir: string; - let testProvider: TestProvider; let identitiesManager: IdentitiesManager; let db: DB; let grpcServer: GRPCServer; @@ -60,8 +46,6 @@ describe('identitiesInfoGet', () => { db, logger, }); - testProvider = new TestProvider(); - identitiesManager.registerProvider(testProvider); const clientService = { identitiesInfoGet: identitiesInfoGet({ authenticate, @@ -93,16 +77,375 @@ describe('identitiesInfoGet', () => { recursive: true, }); }); - test('gets identity', async () => { - const request = new identitiesPB.Provider(); - request.setProviderId(testToken.providerId); - request.setIdentityId(testToken.identityId); - const response = await grpcClient.identitiesInfoGet( + test('gets an identity', async () => { + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.tokenData, + ); + const request = new identitiesPB.ProviderSearch(); + request.setIdentityId(user1.identityId); + const response = grpcClient.identitiesInfoGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + }); + test('searches for a handle across providers', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.tokenData, + ); + const request = new identitiesPB.ProviderSearch(); + request.setIdentityId('user1'); + const response = grpcClient.identitiesInfoGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + provider: { + identityId: user2.identityId, + providerId: user2.providerId, + }, + url: user2.url, + }); + }); + test('searches for identities matching a search term', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'abc', + email: 'abc@test.com', + url: 'provider1.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'def', + email: 'def@test.com', + url: 'provider2.com/user1', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.tokenData, + ); + const request = new identitiesPB.ProviderSearch(); + request.setIdentityId('user1'); + request.setSearchTermList(['abc']); + const response = grpcClient.identitiesInfoGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + }); + test('gets no connected identities', async () => { + // Setup provider + const provider = new TestProvider(); + const user1 = { + providerId: provider.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider.users['user1'] = user1; + const user2 = { + providerId: provider.id, + identityId: 'user2' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider.users['user2'] = user2; + identitiesManager.registerProvider(provider); + await identitiesManager.putToken( + provider.id, + testToken.identityId, + testToken.tokenData, + ); + const request = new identitiesPB.ProviderSearch(); + request.setIdentityId('user1'); + request.setLimit('0'); + const response = grpcClient.identitiesInfoGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(0); + }); + test('gets one connected identity', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.tokenData, + ); + const request = new identitiesPB.ProviderSearch(); + request.setIdentityId('user1'); + request.setLimit('1'); + const response = grpcClient.identitiesInfoGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + }); + test('cannot get more identities than available', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + await identitiesManager.putToken( + provider2.id, + testToken.identityId, + testToken.tokenData, + ); + const request = new identitiesPB.ProviderSearch(); + request.setIdentityId('user1'); + request.setLimit('3'); + const response = grpcClient.identitiesInfoGet( + request, + clientUtils.encodeAuthFromPassword(password), + ); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(2); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); + expect(output[1]).toEqual({ + email: user2.email, + name: user2.name, + provider: { + identityId: user2.identityId, + providerId: user2.providerId, + }, + url: user2.url, + }); + }); + test('can only get from authenticated providers', async () => { + // Setup providers + const provider1 = new TestProvider('provider1' as ProviderId); + const provider2 = new TestProvider('provider2' as ProviderId); + const user1 = { + providerId: provider1.id, + identityId: 'user1' as IdentityId, + name: 'User1', + email: 'user1@test.com', + url: 'test.com/user1', + }; + provider1.users['user1'] = user1; + const user2 = { + providerId: provider2.id, + identityId: 'user1' as IdentityId, + name: 'User2', + email: 'user2@test.com', + url: 'test.com/user2', + }; + provider2.users['user1'] = user2; + identitiesManager.registerProvider(provider1); + identitiesManager.registerProvider(provider2); + await identitiesManager.putToken( + provider1.id, + testToken.identityId, + testToken.tokenData, + ); + const request = new identitiesPB.ProviderSearch(); + request.setIdentityId('user1'); + const response = grpcClient.identitiesInfoGet( request, clientUtils.encodeAuthFromPassword(password), ); - expect(response).toBeInstanceOf(identitiesPB.Provider); - expect(response.getProviderId()).toBe(testToken.providerId); - expect(response.getIdentityId()).toBe(testToken.identityId); + const output = Array(); + for await (const identityInfoMessage of response) { + expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); + const obj = identityInfoMessage.toObject(); + output.push(obj); + } + expect(output).toHaveLength(1); + expect(output[0]).toEqual({ + email: user1.email, + name: user1.name, + provider: { + identityId: user1.identityId, + providerId: user1.providerId, + }, + url: user1.url, + }); }); }); diff --git a/tests/client/service/identitiesInfoGetConnected.test.ts b/tests/client/service/identitiesInfoGetConnected.test.ts deleted file mode 100644 index 7a40b97a4..000000000 --- a/tests/client/service/identitiesInfoGetConnected.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import type { IdentityData, IdentityId, ProviderId } from '@/identities/types'; -import type { Host, Port } from '@/network/types'; -import fs from 'fs'; -import path from 'path'; -import os from 'os'; -import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { Metadata } from '@grpc/grpc-js'; -import { DB } from '@matrixai/db'; -import { GRPCServer } from '@/grpc'; -import { IdentitiesManager } from '@/identities'; -import { - GRPCClientClient, - ClientServiceService, - utils as clientUtils, -} from '@/client'; -import identitiesInfoGetConnected from '@/client/service/identitiesInfoGetConnected'; -import * as identitiesPB from '@/proto/js/polykey/v1/identities/identities_pb'; -import { utils as nodesUtils } from '@/nodes'; -import TestProvider from '../../identities/TestProvider'; - -describe('identitiesInfoGetConnected', () => { - const logger = new Logger('identitiesInfoGetConnected test', LogLevel.WARN, [ - new StreamHandler(), - ]); - const password = 'helloworld'; - const authenticate = async (metaClient, metaServer = new Metadata()) => - metaServer; - const testToken = { - providerId: 'test-provider' as ProviderId, - identityId: 'test_user' as IdentityId, - tokenData: { - accessToken: 'abc123', - }, - }; - const user1: IdentityData = { - providerId: testToken.providerId, - identityId: 'user1' as IdentityId, - name: 'User1', - email: 'user1@test.com', - url: 'test.com/user1', - }; - const user2: IdentityData = { - providerId: testToken.providerId, - identityId: 'user2' as IdentityId, - name: 'User2', - email: 'user2@test.com', - url: 'test.com/user2', - }; - const getConnectedInfos = async function* (): AsyncGenerator { - yield user1; - yield user2; - }; - let mockedGetConnectedIdentityDatas: jest.SpyInstance; - beforeAll(async () => { - mockedGetConnectedIdentityDatas = jest - .spyOn(TestProvider.prototype, 'getConnectedIdentityDatas') - .mockImplementation(getConnectedInfos); - }); - afterAll(async () => { - mockedGetConnectedIdentityDatas.mockRestore(); - }); - let dataDir: string; - let testProvider: TestProvider; - let identitiesManager: IdentitiesManager; - let db: DB; - let grpcServer: GRPCServer; - let grpcClient: GRPCClientClient; - beforeEach(async () => { - dataDir = await fs.promises.mkdtemp( - path.join(os.tmpdir(), 'polykey-test-'), - ); - const dbPath = path.join(dataDir, 'db'); - db = await DB.createDB({ - dbPath, - logger, - }); - identitiesManager = await IdentitiesManager.createIdentitiesManager({ - db, - logger, - }); - testProvider = new TestProvider(); - identitiesManager.registerProvider(testProvider); - const clientService = { - identitiesInfoGetConnected: identitiesInfoGetConnected({ - authenticate, - identitiesManager, - }), - }; - grpcServer = new GRPCServer({ logger }); - await grpcServer.start({ - services: [[ClientServiceService, clientService]], - host: '127.0.0.1' as Host, - port: 0 as Port, - }); - grpcClient = await GRPCClientClient.createGRPCClientClient({ - nodeId: nodesUtils.decodeNodeId( - 'vrcacp9vsb4ht25hds6s4lpp2abfaso0mptcfnh499n35vfcn2gkg', - )!, - host: '127.0.0.1' as Host, - port: grpcServer.port, - logger, - }); - }); - afterEach(async () => { - await grpcClient.destroy(); - await grpcServer.stop(); - await identitiesManager.stop(); - await db.stop(); - await fs.promises.rm(dataDir, { - force: true, - recursive: true, - }); - }); - test('gets connected identities', async () => { - // Needs an authenticated identity - await identitiesManager.putToken( - testToken.providerId, - testToken.identityId, - testToken.tokenData, - ); - const request = new identitiesPB.ProviderSearch(); - const provider = new identitiesPB.Provider(); - provider.setProviderId(testToken.providerId); - provider.setIdentityId(testToken.identityId); - request.setProvider(provider); - request.setSearchTermList([]); - const response = grpcClient.identitiesInfoGetConnected( - request, - clientUtils.encodeAuthFromPassword(password), - ); - const output = Array(); - for await (const identityInfoMessage of response) { - expect(identityInfoMessage).toBeInstanceOf(identitiesPB.Info); - const obj = identityInfoMessage.toObject(); - output.push(obj); - } - expect(output[0]).toEqual({ - email: user1.email, - name: user1.name, - provider: { - identityId: user1.identityId, - providerId: user1.providerId, - }, - url: user1.url, - }); - expect(output[1]).toEqual({ - email: user2.email, - name: user2.name, - provider: { - identityId: user2.identityId, - providerId: user2.providerId, - }, - url: user2.url, - }); - // Unauthenticate - await identitiesManager.delToken( - testToken.providerId, - testToken.identityId, - ); - }); -}); diff --git a/tests/discovery/Discovery.test.ts b/tests/discovery/Discovery.test.ts index 9be398c32..4d3270b44 100644 --- a/tests/discovery/Discovery.test.ts +++ b/tests/discovery/Discovery.test.ts @@ -1,26 +1,29 @@ import type { ClaimLinkIdentity } from '@/claims/types'; import type { IdentityId, ProviderId } from '@/identities/types'; import type { Host, Port } from '@/network/types'; +import type { Gestalt } from '@/gestalts/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; -import { destroyed } from '@matrixai/async-init'; import { DB } from '@matrixai/db'; import { PolykeyAgent } from '@'; -import { utils as claimsUtils } from '@/claims'; -import { Discovery, errors as discoveryErrors } from '@/discovery'; +import { Discovery } from '@/discovery'; import { GestaltGraph } from '@/gestalts'; import { IdentitiesManager } from '@/identities'; import { NodeConnectionManager, NodeGraph, NodeManager } from '@/nodes'; -import { KeyManager, utils as keysUtils } from '@/keys'; +import { KeyManager } from '@/keys'; import { ACL } from '@/acl'; import { Sigchain } from '@/sigchain'; import { ForwardProxy, ReverseProxy } from '@/network'; -import { utils as nodesUtils } from '@/nodes'; -import TestProvider from '../identities/TestProvider'; -import * as testUtils from '../utils'; +import { poll } from '@/utils'; +import * as nodesUtils from '@/nodes/utils'; +import * as claimsUtils from '@/claims/utils'; +import * as discoveryErrors from '@/discovery/errors'; +import * as keysUtils from '@/keys/utils'; import * as testNodesUtils from '../nodes/utils'; +import * as testUtils from '../utils'; +import TestProvider from '../identities/TestProvider'; describe('Discovery', () => { const password = 'password'; @@ -99,6 +102,11 @@ describe('Discovery', () => { logger: logger.getChild('identities'), }); identitiesManager.registerProvider(testProvider); + await identitiesManager.putToken( + testToken.providerId, + testToken.identityId, + testToken.tokenData, + ); sigchain = await Sigchain.createSigchain({ db, keyManager, @@ -174,6 +182,7 @@ describe('Discovery', () => { await nodeA.identitiesManager.putToken(testToken.providerId, identityId, { accessToken: 'def456', }); + testProvider.users[identityId] = {}; const identityClaim: ClaimLinkIdentity = { type: 'identity', node: nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), @@ -186,9 +195,11 @@ describe('Discovery', () => { }, global.maxTimeout); afterAll(async () => { await nodeA.stop(); + await nodeA.destroy(); await nodeB.stop(); - await nodeGraph.stop(); + await nodeB.destroy(); await nodeConnectionManager.stop(); + await nodeGraph.stop(); await revProxy.stop(); await fwdProxy.stop(); await sigchain.stop(); @@ -206,6 +217,7 @@ describe('Discovery', () => { }); test('discovery readiness', async () => { const discovery = await Discovery.createDiscovery({ + db, keyManager, gestaltGraph, identitiesManager, @@ -213,18 +225,22 @@ describe('Discovery', () => { sigchain, logger, }); - expect(discovery[destroyed]).toBeFalsy(); + await expect(discovery.destroy()).rejects.toThrow( + discoveryErrors.ErrorDiscoveryRunning, + ); + await discovery.start(); + await discovery.stop(); await discovery.destroy(); - expect(discovery[destroyed]).toBeTruthy(); - expect(() => { - discovery.discoverGestaltByIdentity('' as ProviderId, '' as IdentityId); - }).toThrow(discoveryErrors.ErrorDiscoveryDestroyed); - expect(() => { - discovery.discoverGestaltByNode(testUtils.generateRandomNodeId()); - }).toThrow(discoveryErrors.ErrorDiscoveryDestroyed); + await expect( + discovery.queueDiscoveryByIdentity('' as ProviderId, '' as IdentityId), + ).rejects.toThrow(discoveryErrors.ErrorDiscoveryNotRunning); + await expect( + discovery.queueDiscoveryByNode(testUtils.generateRandomNodeId()), + ).rejects.toThrow(discoveryErrors.ErrorDiscoveryNotRunning); }); test('discovery by node', async () => { const discovery = await Discovery.createDiscovery({ + db, keyManager, gestaltGraph, identitiesManager, @@ -232,14 +248,34 @@ describe('Discovery', () => { sigchain, logger, }); - const discoverProcess = discovery.discoverGestaltByNode( - nodeA.keyManager.getNodeId(), + await discovery.queueDiscoveryByNode(nodeA.keyManager.getNodeId()); + const gestalt = await poll( + async () => { + const gestalts = await poll>( + async () => { + return await gestaltGraph.getGestalts(); + }, + (_, result) => { + if (result.length === 1) return true; + return false; + }, + 100, + ); + return gestalts[0]; + }, + (_, result) => { + if (result === undefined) return false; + if (Object.keys(result.matrix).length === 3) return true; + return false; + }, + 100, ); - for await (const _step of discoverProcess) { - // Waiting for the discovery process to finish. - } - const gestalt = await gestaltGraph.getGestalts(); - expect(gestalt.length).not.toBe(0); + const gestaltMatrix = gestalt.matrix; + const gestaltNodes = gestalt.nodes; + const gestaltIdentities = gestalt.identities; + expect(Object.keys(gestaltMatrix)).toHaveLength(3); + expect(Object.keys(gestaltNodes)).toHaveLength(2); + expect(Object.keys(gestaltIdentities)).toHaveLength(1); const gestaltString = JSON.stringify(gestalt); expect(gestaltString).toContain( nodesUtils.encodeNodeId(nodeA.keyManager.getNodeId()), @@ -248,17 +284,16 @@ describe('Discovery', () => { nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), ); expect(gestaltString).toContain(identityId); + // Reverse side-effects + await gestaltGraph.unsetNode(nodeA.keyManager.getNodeId()); + await gestaltGraph.unsetNode(nodeB.keyManager.getNodeId()); + await gestaltGraph.unsetIdentity(testToken.providerId, identityId); + await discovery.stop(); await discovery.destroy(); - await gestaltGraph.stop(); - await gestaltGraph.destroy(); - gestaltGraph = await GestaltGraph.createGestaltGraph({ - db, - acl, - logger, - }); }); test('discovery by identity', async () => { const discovery = await Discovery.createDiscovery({ + db, keyManager, gestaltGraph, identitiesManager, @@ -266,14 +301,34 @@ describe('Discovery', () => { sigchain, logger, }); - const discoverProcess = discovery.discoverGestaltByNode( - nodeA.keyManager.getNodeId(), + await discovery.queueDiscoveryByIdentity(testToken.providerId, identityId); + const gestalt = await poll( + async () => { + const gestalts = await poll>( + async () => { + return await gestaltGraph.getGestalts(); + }, + (_, result) => { + if (result.length === 1) return true; + return false; + }, + 100, + ); + return gestalts[0]; + }, + (_, result) => { + if (result === undefined) return false; + if (Object.keys(result.matrix).length === 3) return true; + return false; + }, + 100, ); - for await (const _step of discoverProcess) { - // Waiting for the discovery process to finish. - } - const gestalt = await gestaltGraph.getGestalts(); - expect(gestalt.length).not.toBe(0); + const gestaltMatrix = gestalt.matrix; + const gestaltNodes = gestalt.nodes; + const gestaltIdentities = gestalt.identities; + expect(Object.keys(gestaltMatrix)).toHaveLength(3); + expect(Object.keys(gestaltNodes)).toHaveLength(2); + expect(Object.keys(gestaltIdentities)).toHaveLength(1); const gestaltString = JSON.stringify(gestalt); expect(gestaltString).toContain( nodesUtils.encodeNodeId(nodeA.keyManager.getNodeId()), @@ -282,13 +337,177 @@ describe('Discovery', () => { nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), ); expect(gestaltString).toContain(identityId); + // Reverse side-effects + await gestaltGraph.unsetNode(nodeA.keyManager.getNodeId()); + await gestaltGraph.unsetNode(nodeB.keyManager.getNodeId()); + await gestaltGraph.unsetIdentity(testToken.providerId, identityId); + await discovery.stop(); await discovery.destroy(); - await gestaltGraph.stop(); - await gestaltGraph.destroy(); - gestaltGraph = await GestaltGraph.createGestaltGraph({ + }); + test('updates previously discovered gestalts', async () => { + const discovery = await Discovery.createDiscovery({ db, - acl, + keyManager, + gestaltGraph, + identitiesManager, + nodeManager, + sigchain, + logger, + }); + await discovery.queueDiscoveryByNode(nodeA.keyManager.getNodeId()); + const gestalt1 = await poll( + async () => { + const gestalts = await poll>( + async () => { + return await gestaltGraph.getGestalts(); + }, + (_, result) => { + if (result.length === 1) return true; + return false; + }, + 100, + ); + return gestalts[0]; + }, + (_, result) => { + if (result === undefined) return false; + if (Object.keys(result.matrix).length === 3) return true; + return false; + }, + 100, + ); + const gestaltMatrix1 = gestalt1.matrix; + const gestaltNodes1 = gestalt1.nodes; + const gestaltIdentities1 = gestalt1.identities; + expect(Object.keys(gestaltMatrix1)).toHaveLength(3); + expect(Object.keys(gestaltNodes1)).toHaveLength(2); + expect(Object.keys(gestaltIdentities1)).toHaveLength(1); + const gestaltString1 = JSON.stringify(gestalt1); + expect(gestaltString1).toContain( + nodesUtils.encodeNodeId(nodeA.keyManager.getNodeId()), + ); + expect(gestaltString1).toContain( + nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), + ); + expect(gestaltString1).toContain(identityId); + // Add another linked identity + const identityId2 = 'other-gestalt2' as IdentityId; + await nodeA.identitiesManager.putToken(testToken.providerId, identityId2, { + accessToken: 'ghi789', + }); + testProvider.users[identityId2] = {}; + const identityClaim: ClaimLinkIdentity = { + type: 'identity', + node: nodesUtils.encodeNodeId(nodeA.keyManager.getNodeId()), + provider: testProvider.id, + identity: identityId2, + }; + const [, claimEncoded] = await nodeA.sigchain.addClaim(identityClaim); + const claim = claimsUtils.decodeClaim(claimEncoded); + await testProvider.publishClaim(identityId2, claim); + // Note that eventually we would like to add in a system of revisiting + // already discovered vertices, however for now we must do this manually. + await discovery.queueDiscoveryByNode(nodeA.keyManager.getNodeId()); + const gestalt2 = await poll( + async () => { + const gestalts = await poll>( + async () => { + return await gestaltGraph.getGestalts(); + }, + (_, result) => { + if (result.length === 1) return true; + return false; + }, + 100, + ); + return gestalts[0]; + }, + (_, result) => { + if (result === undefined) return false; + if (Object.keys(result.matrix).length === 4) return true; + return false; + }, + 100, + ); + const gestaltMatrix2 = gestalt2.matrix; + const gestaltNodes2 = gestalt2.nodes; + const gestaltIdentities2 = gestalt2.identities; + expect(Object.keys(gestaltMatrix2)).toHaveLength(4); + expect(Object.keys(gestaltNodes2)).toHaveLength(2); + expect(Object.keys(gestaltIdentities2)).toHaveLength(2); + const gestaltString2 = JSON.stringify(gestalt2); + expect(gestaltString2).toContain( + nodesUtils.encodeNodeId(nodeA.keyManager.getNodeId()), + ); + expect(gestaltString2).toContain( + nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), + ); + expect(gestaltString2).toContain(identityId); + expect(gestaltString2).toContain(identityId2); + // Reverse side-effects + await gestaltGraph.unsetNode(nodeA.keyManager.getNodeId()); + await gestaltGraph.unsetNode(nodeB.keyManager.getNodeId()); + await gestaltGraph.unsetIdentity(testToken.providerId, identityId); + await gestaltGraph.unsetIdentity(testToken.providerId, identityId2); + // Can just remove the user that the claim is for as this will cause the + // claim to be dropped during discovery + delete testProvider.users[identityId2]; + await discovery.stop(); + await discovery.destroy(); + }); + test('discovery persistence across restarts', async () => { + const discovery = await Discovery.createDiscovery({ + db, + keyManager, + gestaltGraph, + identitiesManager, + nodeManager, + sigchain, logger, }); + await discovery.queueDiscoveryByNode(nodeA.keyManager.getNodeId()); + await discovery.stop(); + await discovery.start(); + const gestalt = await poll( + async () => { + const gestalts = await poll>( + async () => { + return await gestaltGraph.getGestalts(); + }, + (_, result) => { + if (result.length === 1) return true; + return false; + }, + 100, + ); + return gestalts[0]; + }, + (_, result) => { + if (result === undefined) return false; + if (Object.keys(result.matrix).length === 3) return true; + return false; + }, + 100, + ); + const gestaltMatrix = gestalt.matrix; + const gestaltNodes = gestalt.nodes; + const gestaltIdentities = gestalt.identities; + expect(Object.keys(gestaltMatrix)).toHaveLength(3); + expect(Object.keys(gestaltNodes)).toHaveLength(2); + expect(Object.keys(gestaltIdentities)).toHaveLength(1); + const gestaltString = JSON.stringify(gestalt); + expect(gestaltString).toContain( + nodesUtils.encodeNodeId(nodeA.keyManager.getNodeId()), + ); + expect(gestaltString).toContain( + nodesUtils.encodeNodeId(nodeB.keyManager.getNodeId()), + ); + expect(gestaltString).toContain(identityId); + // Reverse side-effects + await gestaltGraph.unsetNode(nodeA.keyManager.getNodeId()); + await gestaltGraph.unsetNode(nodeB.keyManager.getNodeId()); + await gestaltGraph.unsetIdentity(testToken.providerId, identityId); + await discovery.stop(); + await discovery.destroy(); }); }); diff --git a/tests/gestalts/GestaltGraph.test.ts b/tests/gestalts/GestaltGraph.test.ts index 7cd3ab7a4..fa30c86bd 100644 --- a/tests/gestalts/GestaltGraph.test.ts +++ b/tests/gestalts/GestaltGraph.test.ts @@ -14,15 +14,12 @@ import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; - -import { - GestaltGraph, - utils as gestaltsUtils, - errors as gestaltErrors, -} from '@/gestalts'; +import { GestaltGraph } from '@/gestalts'; import { ACL } from '@/acl'; +import * as gestaltsErrors from '@/gestalts/errors'; +import * as gestaltsUtils from '@/gestalts/utils'; import * as keysUtils from '@/keys/utils'; -import { utils as nodesUtils } from '@/nodes'; +import * as nodesUtils from '@/nodes/utils'; import * as testUtils from '../utils'; describe('GestaltGraph', () => { @@ -152,17 +149,17 @@ describe('GestaltGraph', () => { logger, }); await expect(gestaltGraph.destroy()).rejects.toThrow( - gestaltErrors.ErrorGestaltsGraphRunning, + gestaltsErrors.ErrorGestaltsGraphRunning, ); // Should be a noop await gestaltGraph.start(); await gestaltGraph.stop(); await gestaltGraph.destroy(); await expect(gestaltGraph.start()).rejects.toThrow( - gestaltErrors.ErrorGestaltsGraphDestroyed, + gestaltsErrors.ErrorGestaltsGraphDestroyed, ); await expect(gestaltGraph.getGestalts()).rejects.toThrow( - gestaltErrors.ErrorGestaltsGraphNotRunning, + gestaltsErrors.ErrorGestaltsGraphNotRunning, ); }); test('get, set and unset node', async () => { @@ -536,17 +533,23 @@ describe('GestaltGraph', () => { acl, logger, }); - const nodeInfo: NodeInfo = { + const nodeInfo1: NodeInfo = { id: nodeIdABCEncoded, chain: {}, }; - await gestaltGraph.setNode(nodeInfo); + const nodeInfo2: NodeInfo = { + id: nodeIdDEFEncoded, + chain: {}, + }; const identityInfo: IdentityInfo = { providerId: 'github.com' as ProviderId, identityId: 'abc' as IdentityId, claims: {}, }; + await gestaltGraph.setNode(nodeInfo1); + await gestaltGraph.setNode(nodeInfo2); await gestaltGraph.setIdentity(identityInfo); + await gestaltGraph.linkNodeAndIdentity(nodeInfo1, identityInfo); const gestalts = await gestaltGraph.getGestalts(); const identityGestalt = await gestaltGraph.getGestaltByIdentity( identityInfo.providerId, @@ -558,11 +561,12 @@ describe('GestaltGraph', () => { expect(gestalts).toHaveLength(2); // Check if the two combine after linking. - await gestaltGraph.linkNodeAndIdentity(nodeInfo, identityInfo); + await gestaltGraph.linkNodeAndNode(nodeInfo1, nodeInfo2); const gestalts2 = await gestaltGraph.getGestalts(); expect(gestalts2).toHaveLength(1); const gestalts2String = JSON.stringify(gestalts2[0]); - expect(gestalts2String).toContain(nodeInfo.id); + expect(gestalts2String).toContain(nodeInfo1.id); + expect(gestalts2String).toContain(nodeInfo2.id); expect(gestalts2String).toContain(identityInfo.providerId); expect(gestalts2String).toContain(identityInfo.identityId); diff --git a/tests/identities/IdentitiesManager.test.ts b/tests/identities/IdentitiesManager.test.ts index d1933b658..b7ca969b0 100644 --- a/tests/identities/IdentitiesManager.test.ts +++ b/tests/identities/IdentitiesManager.test.ts @@ -12,11 +12,10 @@ import path from 'path'; import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { DB } from '@matrixai/db'; - import { IdentitiesManager, providers } from '@/identities'; import * as identitiesErrors from '@/identities/errors'; import * as keysUtils from '@/keys/utils'; -import { utils as nodesUtils } from '@/nodes'; +import * as nodesUtils from '@/nodes/utils'; import TestProvider from './TestProvider'; import * as testUtils from '../utils'; @@ -208,6 +207,9 @@ describe('IdentitiesManager', () => { expect(identityData).toBeDefined(); expect(identityData).toHaveProperty('providerId', testProvider.id); expect(identityData).toHaveProperty('identityId', identityId); + // Give the provider a connected identity to discover + testProvider.users['some-user'] = {}; + testProvider.users[identityId].connected = ['some-user']; const identityDatas: Array = []; for await (const identityData_ of testProvider.getConnectedIdentityDatas( identityId, diff --git a/tests/identities/TestProvider.ts b/tests/identities/TestProvider.ts index a26567eb5..0678f70ad 100644 --- a/tests/identities/TestProvider.ts +++ b/tests/identities/TestProvider.ts @@ -4,18 +4,20 @@ import type { IdentityId, TokenData, IdentityData, + IdentityClaim, + IdentityClaimId, ProviderAuthenticateRequest, } from '@/identities/types'; import type { Claim } from '@/claims/types'; -import type { IdentityClaim, IdentityClaimId } from '@/identities/types'; - -import { Provider, errors as identitiesErrors } from '@/identities'; +import { Provider } from '@/identities'; +import * as identitiesUtils from '@/identities/utils'; +import * as identitiesErrors from '@/identities/errors'; class TestProvider extends Provider { - public readonly id = 'test-provider' as ProviderId; + public readonly id: ProviderId; public linkIdCounter: number = 0; - protected users: Record; // FIXME: the string union on VaultId is to prevent some false errors. + public users: Record; // FIXME: the string union on VaultId is to prevent some false errors. public links: Record; // FIXME: the string union on VaultId is to prevent some false errors. protected userLinks: Record< IdentityId | string, @@ -23,14 +25,13 @@ class TestProvider extends Provider { >; // FIXME: the string union on VaultId is to prevent some false errors. protected userTokens: Record; - public constructor() { + public constructor(providerId: ProviderId = 'test-provider' as ProviderId) { super(); + this.id = providerId; this.users = { test_user: { email: 'test_user@test.com', - }, - test_user2: { - email: 'test_user2@test.com', + connected: ['connected_identity'], }, }; this.userTokens = { @@ -91,12 +92,15 @@ class TestProvider extends Provider { return { providerId: this.id, identityId: identityId, + name: user.name ?? undefined, email: user.email ?? undefined, + url: user.url ?? undefined, }; } public async *getConnectedIdentityDatas( authIdentityId: IdentityId, + searchTerms: Array = [], ): AsyncGenerator { let tokenData = await this.getToken(authIdentityId); if (!tokenData) { @@ -106,16 +110,27 @@ class TestProvider extends Provider { } tokenData = await this.checkToken(tokenData, authIdentityId); for (const [k, v] of Object.entries(this.users) as Array< - [IdentityId, { email: string }] + [ + IdentityId, + { name: string; email: string; url: string; connected: Array }, + ] >) { if (k === authIdentityId) { continue; } - yield { + if (!this.users[authIdentityId].connected.includes(k)) { + continue; + } + const data: IdentityData = { providerId: this.id, identityId: k, + name: v.name ?? undefined, email: v.email ?? undefined, + url: v.url ?? undefined, }; + if (identitiesUtils.matchIdentityData(data, searchTerms)) { + yield data; + } } return; } @@ -134,7 +149,10 @@ class TestProvider extends Provider { const linkId = this.linkIdCounter.toString() as IdentityClaimId; this.linkIdCounter++; this.links[linkId] = JSON.stringify(identityClaim); - const links = this.userLinks[authIdentityId] ?? []; + this.userLinks[authIdentityId] = this.userLinks[authIdentityId] + ? this.userLinks[authIdentityId] + : []; + const links = this.userLinks[authIdentityId]; links.push(linkId); return { ...identityClaim,