diff --git a/src/PolykeyAgent.ts b/src/PolykeyAgent.ts index 95dbe414d..5dff3aa9c 100644 --- a/src/PolykeyAgent.ts +++ b/src/PolykeyAgent.ts @@ -449,6 +449,7 @@ class PolykeyAgent { notificationsManager: this.notificationsManager, sessionManager: this.sessionManager, vaultManager: this.vaultManager, + sigchain: this.sigchain, grpcServerClient: this.grpcServerClient, grpcServerAgent: this.grpcServerAgent, fwdProxy: this.fwdProxy, diff --git a/src/bin/identities/CommandAllow.ts b/src/bin/identities/CommandAllow.ts index 04d0190de..dc1fada57 100644 --- a/src/bin/identities/CommandAllow.ts +++ b/src/bin/identities/CommandAllow.ts @@ -72,7 +72,7 @@ class CommandAllow extends CommandPolykey { // Setting By Identity const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(gestaltId.providerId); - providerMessage.setMessage(gestaltId.identityId); + providerMessage.setIdentityId(gestaltId.identityId); setActionMessage.setIdentity(providerMessage); await binUtils.retryAuthentication( (auth) => diff --git a/src/bin/identities/CommandAuthenticate.ts b/src/bin/identities/CommandAuthenticate.ts index dd19ace19..1ecebc0de 100644 --- a/src/bin/identities/CommandAuthenticate.ts +++ b/src/bin/identities/CommandAuthenticate.ts @@ -3,6 +3,7 @@ import CommandPolykey from '../CommandPolykey'; import * as binUtils from '../utils'; import * as binOptions from '../utils/options'; import * as binProcessors from '../utils/processors'; +import * as identitiesUtils from '../../identities/utils'; class CommandAuthenticate extends CommandPolykey { constructor(...args: ConstructorParameters) { @@ -32,7 +33,11 @@ class CommandAuthenticate extends CommandPolykey { this.fs, ); let pkClient: PolykeyClient; + let genReadable: ReturnType< + typeof pkClient.grpcClient.identitiesAuthenticate + >; this.exitHandlers.handlers.push(async () => { + if (genReadable != null) genReadable.stream.cancel(); if (pkClient != null) await pkClient.stop(); }); try { @@ -45,32 +50,50 @@ class CommandAuthenticate extends CommandPolykey { }); const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(providerId); - providerMessage.setMessage(identityId); - const successMessage = await binUtils.retryAuthentication( - async (auth) => { - const stream = pkClient.grpcClient.identitiesAuthenticate( - providerMessage, - auth, - ); - const codeMessage = (await stream.next()).value; - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [`Your device code is: ${codeMessage!.getMessage()}`], - }), - ); - return (await stream.next()).value; - }, - meta, - ); - process.stdout.write( - binUtils.outputFormatter({ - type: options.format === 'json' ? 'json' : 'list', - data: [ - `Successfully authenticated user: ${successMessage!.getMessage()}`, - ], - }), - ); + providerMessage.setIdentityId(identityId); + await binUtils.retryAuthentication(async (auth) => { + genReadable = pkClient.grpcClient.identitiesAuthenticate( + providerMessage, + auth, + ); + for await (const message of genReadable) { + switch (message.getStepCase()) { + case identitiesPB.AuthenticationProcess.StepCase.REQUEST: { + const authRequest = message.getRequest()!; + this.logger.info( + `Navigate to the URL in order to authenticate`, + ); + this.logger.info( + 'Use any additional additional properties to complete authentication', + ); + identitiesUtils.browser(authRequest.getUrl()); + process.stdout.write( + binUtils.outputFormatter({ + type: options.format === 'json' ? 'json' : 'dict', + data: { + url: authRequest.getUrl(), + ...Object.fromEntries(authRequest.getDataMap().entries()), + }, + }), + ); + break; + } + case identitiesPB.AuthenticationProcess.StepCase.RESPONSE: { + const authResponse = message.getResponse()!; + this.logger.info( + `Authenticated digital identity provider ${providerId} with identity ${identityId}`, + ); + process.stdout.write( + binUtils.outputFormatter({ + type: options.format === 'json' ? 'json' : 'list', + data: [authResponse.getIdentityId()], + }), + ); + break; + } + } + } + }, meta); } finally { if (pkClient! != null) await pkClient.stop(); } diff --git a/src/bin/identities/CommandClaim.ts b/src/bin/identities/CommandClaim.ts index ba5ae6241..205f869bf 100644 --- a/src/bin/identities/CommandClaim.ts +++ b/src/bin/identities/CommandClaim.ts @@ -45,7 +45,7 @@ class CommandClaim extends CommandPolykey { }); const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(providerId); - providerMessage.setMessage(identityId); + providerMessage.setIdentityId(identityId); await binUtils.retryAuthentication( (auth) => pkClient.grpcClient.identitiesClaim(providerMessage, auth), meta, diff --git a/src/bin/identities/CommandDisallow.ts b/src/bin/identities/CommandDisallow.ts index 3f143b97d..6f2889bde 100644 --- a/src/bin/identities/CommandDisallow.ts +++ b/src/bin/identities/CommandDisallow.ts @@ -72,7 +72,7 @@ class CommandDisallow extends CommandPolykey { // Setting by Identity const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(gestaltId.providerId); - providerMessage.setMessage(gestaltId.identityId); + providerMessage.setIdentityId(gestaltId.identityId); setActionMessage.setIdentity(providerMessage); // Trusting. await binUtils.retryAuthentication( diff --git a/src/bin/identities/CommandDiscover.ts b/src/bin/identities/CommandDiscover.ts index 748ba142e..ab8aa2b7b 100644 --- a/src/bin/identities/CommandDiscover.ts +++ b/src/bin/identities/CommandDiscover.ts @@ -63,7 +63,7 @@ class CommandDiscover extends CommandPolykey { // Discovery by Identity const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(gestaltId.providerId); - providerMessage.setMessage(gestaltId.identityId); + providerMessage.setIdentityId(gestaltId.identityId); await binUtils.retryAuthentication( (auth) => pkClient.grpcClient.gestaltsDiscoveryByIdentity( diff --git a/src/bin/identities/CommandGet.ts b/src/bin/identities/CommandGet.ts index ef0cfcc1d..1b82616ad 100644 --- a/src/bin/identities/CommandGet.ts +++ b/src/bin/identities/CommandGet.ts @@ -65,7 +65,7 @@ class CommandGet extends CommandPolykey { // Getting from identity. const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(gestaltId.providerId); - providerMessage.setMessage(gestaltId.identityId); + providerMessage.setIdentityId(gestaltId.identityId); res = await binUtils.retryAuthentication( (auth) => pkClient.grpcClient.gestaltsGestaltGetByIdentity( @@ -88,12 +88,7 @@ class CommandGet extends CommandPolykey { // Listing identities for (const identityKey of Object.keys(gestalt.identities)) { const identity = gestalt.identities[identityKey]; - output.push( - parsers.formatIdentityString( - identity.providerId, - identity.identityId, - ), - ); + output.push(`${identity.providerId}:${identity.identityId}`); } } process.stdout.write( diff --git a/src/bin/identities/CommandList.ts b/src/bin/identities/CommandList.ts index ff0a353f0..9b52641c1 100644 --- a/src/bin/identities/CommandList.ts +++ b/src/bin/identities/CommandList.ts @@ -2,7 +2,6 @@ import type PolykeyClient from '../../PolykeyClient'; 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 CommandList extends CommandPolykey { @@ -90,25 +89,18 @@ class CommandList extends CommandPolykey { for (const gestalt of gestalts) { output.push(`gestalt ${count}`); output.push(`permissions: ${gestalt.permissions ?? 'None'}`); - // Listing nodes for (const node of gestalt.nodes) { output.push(`${node.id}`); } // Listing identities for (const identity of gestalt.identities) { - output.push( - parsers.formatIdentityString( - identity.providerId, - identity.identityId, - ), - ); + output.push(`${identity.providerId}:${identity.identityId}`); } output.push(''); count++; } } - process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', diff --git a/src/bin/identities/CommandPermissions.ts b/src/bin/identities/CommandPermissions.ts index b4509efb6..96a1839e3 100644 --- a/src/bin/identities/CommandPermissions.ts +++ b/src/bin/identities/CommandPermissions.ts @@ -48,7 +48,7 @@ class CommandPermissions extends CommandPolykey { port: clientOptions.clientPort, logger: this.logger.getChild(PolykeyClient.name), }); - let actions; + let actions: string[] = []; if (gestaltId.nodeId) { // Getting by Node. const nodeMessage = new nodesPB.Node(); @@ -63,7 +63,7 @@ class CommandPermissions extends CommandPolykey { // Getting by Identity const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(gestaltId.providerId); - providerMessage.setMessage(gestaltId.identityId); + providerMessage.setIdentityId(gestaltId.identityId); const res = await binUtils.retryAuthentication( (auth) => pkClient.grpcClient.gestaltsActionsGetByIdentity( diff --git a/src/bin/identities/CommandSearch.ts b/src/bin/identities/CommandSearch.ts index c6bc660d1..03b5a4393 100644 --- a/src/bin/identities/CommandSearch.ts +++ b/src/bin/identities/CommandSearch.ts @@ -1,9 +1,7 @@ -import type { ProviderId, IdentityId } from '../../identities/types'; import type PolykeyClient from '../../PolykeyClient'; 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 { @@ -54,15 +52,16 @@ class CommandSearch extends CommandPolykey { 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'); + } process.stdout.write( binUtils.outputFormatter({ type: options.format === 'json' ? 'json' : 'list', - data: [ - parsers.formatIdentityString( - res.getProviderId() as ProviderId, - res.getMessage() as IdentityId, - ), - ], + data: [output], }), ); } finally { diff --git a/src/bin/identities/CommandTrust.ts b/src/bin/identities/CommandTrust.ts index 8e4f8d57f..9499e5924 100644 --- a/src/bin/identities/CommandTrust.ts +++ b/src/bin/identities/CommandTrust.ts @@ -71,7 +71,7 @@ class CommandTrust extends CommandPolykey { // Setting by Identity const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(gestaltId.providerId!); - providerMessage.setMessage(gestaltId.identityId!); + providerMessage.setIdentityId(gestaltId.identityId!); setActionMessage.setIdentity(providerMessage); await binUtils.retryAuthentication( (auth) => diff --git a/src/bin/identities/CommandUntrust.ts b/src/bin/identities/CommandUntrust.ts index 22e331203..53e45e997 100644 --- a/src/bin/identities/CommandUntrust.ts +++ b/src/bin/identities/CommandUntrust.ts @@ -71,7 +71,7 @@ class CommandUntrust extends CommandPolykey { // Setting by Identity const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(gestaltId.providerId!); - providerMessage.setMessage(gestaltId.identityId!); + providerMessage.setIdentityId(gestaltId.identityId!); setActionMessage.setIdentity(providerMessage); await binUtils.retryAuthentication( (auth) => diff --git a/src/bin/utils/parsers.ts b/src/bin/utils/parsers.ts index 01b10cc6e..7122042c2 100644 --- a/src/bin/utils/parsers.ts +++ b/src/bin/utils/parsers.ts @@ -56,10 +56,4 @@ function parseIdentityString(identityString: string): { return { providerId, identityId }; } -function formatIdentityString( - providerId: ProviderId, - identityId: IdentityId, -): string { - return `${providerId}:${identityId}`; -} -export { parseNumber, parseSecretPath, parseGestaltId, formatIdentityString }; +export { parseNumber, parseSecretPath, parseGestaltId }; diff --git a/src/client/GRPCClientClient.ts b/src/client/GRPCClientClient.ts index 0eb4e5659..19437c188 100644 --- a/src/client/GRPCClientClient.ts +++ b/src/client/GRPCClientClient.ts @@ -520,7 +520,7 @@ class GRPCClientClient extends GRPCClient { @ready(new clientErrors.ErrorClientClientDestroyed()) public identitiesAuthenticate(...args) { - return grpcUtils.promisifyReadableStreamCall( + return grpcUtils.promisifyReadableStreamCall( this.client, this.client.identitiesAuthenticate, )(...args); diff --git a/src/client/clientService.ts b/src/client/clientService.ts index ec3946311..1d776d09e 100644 --- a/src/client/clientService.ts +++ b/src/client/clientService.ts @@ -7,6 +7,7 @@ import type { GestaltGraph } from '../gestalts'; import type { SessionManager } from '../sessions'; import type { NotificationsManager } from '../notifications'; import type { Discovery } from '../discovery'; +import type { Sigchain } from '../sigchain'; import type { GRPCServer } from '../grpc'; import type { ForwardProxy, ReverseProxy } from '../network'; import type { FileSystem } from '../types'; @@ -43,6 +44,7 @@ function createClientService({ sessionManager, notificationsManager, discovery, + sigchain, grpcServerClient, grpcServerAgent, fwdProxy, @@ -58,6 +60,7 @@ function createClientService({ sessionManager: SessionManager; notificationsManager: NotificationsManager; discovery: Discovery; + sigchain: Sigchain; grpcServerClient: GRPCServer; grpcServerAgent: GRPCServer; fwdProxy: ForwardProxy; @@ -93,7 +96,7 @@ function createClientService({ }), ...createIdentitiesRPC({ identitiesManager, - gestaltGraph, + sigchain, nodeManager, authenticate, }), diff --git a/src/client/errors.ts b/src/client/errors.ts index 691efe9f1..c6a0c39fb 100644 --- a/src/client/errors.ts +++ b/src/client/errors.ts @@ -22,8 +22,9 @@ class ErrorClientAuthDenied extends ErrorClient { exitCode = 77; } -class ErrorClientInvalidNode extends ErrorClient { - exitCode: number = 70; +class ErrorClientInvalidProvider extends ErrorClient { + description = 'Provider Id is invalid or does not exist'; + exitCode = 70; } export { @@ -32,5 +33,5 @@ export { ErrorClientAuthMissing, ErrorClientAuthFormat, ErrorClientAuthDenied, - ErrorClientInvalidNode, + ErrorClientInvalidProvider, }; diff --git a/src/client/rpcGestalts.ts b/src/client/rpcGestalts.ts index afbe69ac6..ab3a21933 100644 --- a/src/client/rpcGestalts.ts +++ b/src/client/rpcGestalts.ts @@ -7,7 +7,6 @@ import type * as grpc from '@grpc/grpc-js'; import type * as clientUtils from './utils'; import type * as nodesPB from '../proto/js/polykey/v1/nodes/nodes_pb'; import type * as identitiesPB from '../proto/js/polykey/v1/identities/identities_pb'; -import * as clientErrors from './errors'; import { makeGestaltAction } from '../gestalts/utils'; @@ -60,7 +59,7 @@ const createGestaltsRPC = ({ const gestalt = await gestaltGraph.getGestaltByIdentity( call.request.getProviderId() as ProviderId, - call.request.getMessage() as IdentityId, + call.request.getIdentityId() as IdentityId, ); if (gestalt != null) { response.setGestaltGraph(JSON.stringify(gestalt)); @@ -129,7 +128,7 @@ const createGestaltsRPC = ({ // Constructing identity info. const gen = discovery.discoverGestaltByIdentity( info.getProviderId() as ProviderId, - info.getMessage() as IdentityId, + info.getIdentityId() as IdentityId, ); for await (const _ of gen) { // Empty @@ -180,7 +179,7 @@ const createGestaltsRPC = ({ call.sendMetadata(metadata); const providerId = info.getProviderId() as ProviderId; - const identityId = info.getMessage() as IdentityId; + const identityId = info.getIdentityId() as IdentityId; const result = await gestaltGraph.getGestaltActionsByIdentity( providerId, identityId, @@ -209,19 +208,6 @@ const createGestaltsRPC = ({ try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Checking - switch (info.getNodeOrProviderCase()) { - default: - case permissionsPB.ActionSet.NodeOrProviderCase - .NODE_OR_PROVIDER_NOT_SET: - case permissionsPB.ActionSet.NodeOrProviderCase.IDENTITY: - throw new clientErrors.ErrorClientInvalidNode( - 'Node not set for SetActionMessage.', - ); - case permissionsPB.ActionSet.NodeOrProviderCase.NODE: - break; // This is fine. - } - // Setting the action. const action = makeGestaltAction(info.getAction()); const nodeId = makeNodeId(info.getNode()?.getNodeId()); @@ -242,23 +228,10 @@ const createGestaltsRPC = ({ try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Checking - switch (info.getNodeOrProviderCase()) { - default: - case permissionsPB.ActionSet.NodeOrProviderCase.NODE: - case permissionsPB.ActionSet.NodeOrProviderCase - .NODE_OR_PROVIDER_NOT_SET: - throw new clientErrors.ErrorClientInvalidNode( - 'Identity not set for SetActionMessage.', - ); - case permissionsPB.ActionSet.NodeOrProviderCase.IDENTITY: - break; // This is fine. - } - // Setting the action. const action = makeGestaltAction(info.getAction()); const providerId = info.getIdentity()?.getProviderId() as ProviderId; - const identityId = info.getIdentity()?.getMessage() as IdentityId; + const identityId = info.getIdentity()?.getIdentityId() as IdentityId; await gestaltGraph.setGestaltActionByIdentity( providerId, identityId, @@ -280,19 +253,6 @@ const createGestaltsRPC = ({ try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Checking - switch (info.getNodeOrProviderCase()) { - default: - case permissionsPB.ActionSet.NodeOrProviderCase - .NODE_OR_PROVIDER_NOT_SET: - case permissionsPB.ActionSet.NodeOrProviderCase.IDENTITY: - throw new clientErrors.ErrorClientInvalidNode( - 'Node not set for SetActionMessage.', - ); - case permissionsPB.ActionSet.NodeOrProviderCase.NODE: - break; // This is fine. - } - // Setting the action. const action = makeGestaltAction(info.getAction()); const nodeId = makeNodeId(info.getNode()?.getNodeId()); @@ -313,23 +273,10 @@ const createGestaltsRPC = ({ try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - // Checking - switch (info.getNodeOrProviderCase()) { - default: - case permissionsPB.ActionSet.NodeOrProviderCase.NODE: - case permissionsPB.ActionSet.NodeOrProviderCase - .NODE_OR_PROVIDER_NOT_SET: - throw new clientErrors.ErrorClientInvalidNode( - 'Identity not set for SetActionMessage.', - ); - case permissionsPB.ActionSet.NodeOrProviderCase.IDENTITY: - break; // This is fine. - } - // Setting the action. const action = makeGestaltAction(info.getAction()); const providerId = info.getIdentity()?.getProviderId() as ProviderId; - const identityId = info.getIdentity()?.getMessage() as IdentityId; + const identityId = info.getIdentity()?.getIdentityId() as IdentityId; await gestaltGraph.unsetGestaltActionByIdentity( providerId, identityId, diff --git a/src/client/rpcIdentities.ts b/src/client/rpcIdentities.ts index 2810208df..aa13785fb 100644 --- a/src/client/rpcIdentities.ts +++ b/src/client/rpcIdentities.ts @@ -1,70 +1,71 @@ +import type * as utils from './utils'; import type { NodeManager } from '../nodes'; -import type { NodeInfo } from '../nodes/types'; -import type { GestaltGraph } from '../gestalts'; +import type { Sigchain } from '../sigchain'; import type { IdentitiesManager } from '../identities'; -import type { - IdentityId, - ProviderId, - TokenData, - IdentityInfo, -} from '../identities/types'; - +import type { IdentityId, ProviderId, TokenData } from '../identities/types'; import type * as grpc from '@grpc/grpc-js'; -import type * as utils from './utils'; -import * as errors from '../errors'; +import * as clientErrors from './errors'; +import * as claimsUtils from '../claims/utils'; import * as grpcUtils from '../grpc/utils'; import * as utilsPB from '../proto/js/polykey/v1/utils/utils_pb'; import * as identitiesPB from '../proto/js/polykey/v1/identities/identities_pb'; +import * as identitiesErrors from '../identities/errors'; +import { never } from '../utils'; const createIdentitiesRPC = ({ identitiesManager, + sigchain, nodeManager, - gestaltGraph, authenticate, }: { identitiesManager: IdentitiesManager; + sigchain: Sigchain; nodeManager: NodeManager; - gestaltGraph: GestaltGraph; authenticate: utils.Authenticate; }) => { return { identitiesAuthenticate: async ( call: grpc.ServerWritableStream< identitiesPB.Provider, - identitiesPB.Provider + identitiesPB.AuthenticationProcess >, ): Promise => { const genWritable = grpcUtils.generatorWritable(call); - const response = new identitiesPB.Provider(); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - const provider = identitiesManager.getProvider( call.request.getProviderId() as ProviderId, ); - const authFlow = provider?.authenticate(); - const userCode = (await authFlow?.next())?.value; - if (typeof userCode !== 'string') { - throw new errors.ErrorProviderAuthentication( - 'userCode was not a string', - ); + if (provider == null) { + throw new clientErrors.ErrorClientInvalidProvider(); } - response.setMessage(userCode); - await genWritable.next(response); - - // Wait to finish. - const userName = (await authFlow?.next())?.value; - if (userName == null) - throw new errors.ErrorProviderAuthentication( - 'Failed to authenticate.', - ); - response.setMessage(userName); - await genWritable.next(response); + const authFlow = provider.authenticate(); + let authFlowResult = await authFlow.next(); + if (authFlowResult.done) { + never(); + } + const authProcess = new identitiesPB.AuthenticationProcess(); + const authRequest = new identitiesPB.AuthenticationRequest(); + authRequest.setUrl(authFlowResult.value.url); + const map = authRequest.getDataMap(); + for (const [k, v] of Object.entries(authFlowResult.value.data)) { + map.set(k, v); + } + authProcess.setRequest(authRequest); + await genWritable.next(authProcess); + authFlowResult = await authFlow.next(); + if (!authFlowResult.done) { + never(); + } + const authResponse = new identitiesPB.AuthenticationResponse(); + authResponse.setIdentityId(authFlowResult.value); + authProcess.setResponse(authResponse); + await genWritable.next(authProcess); await genWritable.next(null); return; - } catch (err) { - await genWritable.throw(err); + } catch (e) { + await genWritable.throw(e); return; } }, @@ -83,7 +84,7 @@ const createIdentitiesRPC = ({ const provider = call.request.getProvider(); await identitiesManager.putToken( provider?.getProviderId() as ProviderId, - provider?.getMessage() as IdentityId, + provider?.getIdentityId() as IdentityId, { accessToken: call.request.getToken() } as TokenData, ); callback(null, response); @@ -104,7 +105,7 @@ const createIdentitiesRPC = ({ const tokens = await identitiesManager.getToken( call.request.getProviderId() as ProviderId, - call.request.getMessage() as IdentityId, + call.request.getIdentityId() as IdentityId, ); response.setToken(JSON.stringify(tokens)); callback(null, response); @@ -125,7 +126,7 @@ const createIdentitiesRPC = ({ await identitiesManager.delToken( call.request.getProviderId() as ProviderId, - call.request.getMessage() as IdentityId, + call.request.getIdentityId() as IdentityId, ); callback(null, response); return; @@ -168,12 +169,10 @@ const createIdentitiesRPC = ({ ?.getProviderId() as ProviderId; const identityId = call.request .getProvider() - ?.getMessage() as IdentityId; + ?.getIdentityId() as IdentityId; const provider = identitiesManager.getProvider(providerId); if (provider == null) - throw Error( - `Provider id: ${providerId} is invalid or provider doesn't exist.`, - ); + throw new clientErrors.ErrorClientInvalidProvider(); const identities = provider.getConnectedIdentityDatas( identityId, @@ -184,7 +183,7 @@ const createIdentitiesRPC = ({ const identityInfoMessage = new identitiesPB.Info(); const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(identity.providerId); - providerMessage.setMessage(identity.identityId); + providerMessage.setIdentityId(identity.identityId); identityInfoMessage.setProvider(providerMessage); identityInfoMessage.setName(identity.name ?? ''); identityInfoMessage.setEmail(identity.email ?? ''); @@ -212,12 +211,13 @@ const createIdentitiesRPC = ({ // Get's an identity out of all identities. const providerId = call.request.getProviderId() as ProviderId; const provider = identitiesManager.getProvider(providerId); - if (provider == null) throw Error(`Invalid provider: ${providerId}`); - const identities = await provider.getAuthIdentityIds(); - if (identities.length !== 0) { + if (provider !== undefined) { + const identities = await provider.getAuthIdentityIds(); response.setProviderId(providerId); - response.setMessage(identities[0]); - } else throw Error(`No identities found for provider: ${providerId}`); + if (identities.length !== 0) { + response.setIdentityId(identities[0]); + } + } callback(null, response); return; } catch (err) { @@ -232,28 +232,30 @@ const createIdentitiesRPC = ({ call: grpc.ServerUnaryCall, callback: grpc.sendUnaryData, ): Promise => { - // To augment a keynode we need a provider, generate an oauthkey and then - const info = call.request; const response = new utilsPB.EmptyMessage(); try { const metadata = await authenticate(call.metadata); call.sendMetadata(metadata); - - const nodeId = nodeManager.getNodeId(); // Getting the local node ID. - - // Do the deed... - const nodeInfo: NodeInfo = { - id: nodeId, - chain: {}, - }; - const identityInfo: IdentityInfo = { - providerId: info.getProviderId() as ProviderId, - identityId: info.getMessage() as IdentityId, - claims: {}, - }; - await gestaltGraph.linkNodeAndIdentity(nodeInfo, identityInfo); // Need to call this - // it takes NodeInfo and IdentityInfo. - // Getting and creating NodeInfo is blocked by + // Check provider is authenticated + const providerId = call.request.getProviderId() as ProviderId; + const provider = identitiesManager.getProvider(providerId); + if (provider == null) + throw new clientErrors.ErrorClientInvalidProvider(); + const identityId = call.request.getIdentityId() as IdentityId; + const identities = await provider.getAuthIdentityIds(); + if (!identities.includes(identityId)) { + throw new identitiesErrors.ErrorProviderUnauthenticated(); + } + // Create identity claim on our node + const claim = await sigchain.addClaim({ + type: 'identity', + node: nodeManager.getNodeId(), + provider: providerId, + identity: identityId, + }); + // Publish claim on identity + const claimDecoded = claimsUtils.decodeClaim(claim); + await provider.publishClaim(identityId, claimDecoded); callback(null, response); return; } catch (err) { diff --git a/src/identities/Provider.ts b/src/identities/Provider.ts index d974753c5..7d6364a21 100644 --- a/src/identities/Provider.ts +++ b/src/identities/Provider.ts @@ -2,8 +2,9 @@ import type { ProviderId, IdentityId, IdentityData, - ProviderTokens, TokenData, + ProviderTokens, + ProviderAuthenticateRequest, } from './types'; import type { Claim } from '../claims/types'; import type { IdentityClaim, IdentityClaimId } from '../identities/types'; @@ -105,11 +106,11 @@ abstract class Provider { * This token must be stored on the token database. * This is a generator that only has 1 step. * This is because we require the caller to perform an authorisation action. - * The return value is the token value. + * The final return value is the identity ID. */ public abstract authenticate( timeout?: number, - ): AsyncGenerator; + ): AsyncGenerator; /** * Refreshes the token diff --git a/src/identities/providers/github/GitHubProvider.ts b/src/identities/providers/github/GitHubProvider.ts index eb3e66668..39f199f32 100644 --- a/src/identities/providers/github/GitHubProvider.ts +++ b/src/identities/providers/github/GitHubProvider.ts @@ -3,6 +3,7 @@ import type { ProviderId, TokenData, IdentityData, + ProviderAuthenticateRequest, } from '../../types'; import type { Claim } from '../../../claims/types'; import type { IdentityClaim, IdentityClaimId } from '../../../identities/types'; @@ -12,7 +13,6 @@ import { Searcher } from 'fast-fuzzy'; import cheerio from 'cheerio'; import Logger from '@matrixai/logger'; import { sleep } from '../../../utils'; -import { browser } from '../../utils'; import Provider from '../../Provider'; import * as identitiesErrors from '../../errors'; @@ -41,7 +41,7 @@ class GitHubProvider extends Provider { public async *authenticate( timeout: number = 120000, - ): AsyncGenerator { + ): AsyncGenerator { const params = new URLSearchParams(); params.set('client_id', this.clientId); params.set('scope', this.scope); @@ -51,7 +51,7 @@ class GitHubProvider extends Provider { method: 'POST', }, ); - this.logger.info('Sending authentication request to Github'); + this.logger.info('Sending authentication request to GitHub'); const response = await fetch(request); if (!response.ok) { throw new identitiesErrors.ErrorProviderAuthentication( @@ -69,8 +69,13 @@ class GitHubProvider extends Provider { `Provider device code request did not return the device_code or the user_code`, ); } - // This code needs to be used by the user to manually enter - yield userCode; + yield { + url: 'https://github.com/login/device', + data: { + // This code needs to be used by the user to manually enter + userCode, + }, + }; // Promise.race does not cancel unfinished promises // the finished condition variable is needed to stop the pollAccessToken process // the pollTimer is needed to stop the pollTimerP @@ -84,7 +89,6 @@ class GitHubProvider extends Provider { }); const that = this; const pollAccessToken = async () => { - browser('https://github.com/login/device'); const payload = new URLSearchParams(); payload.set('grant_type', 'urn:ietf:params:oauth:grant-type:device_code'); payload.set('client_id', that.clientId); @@ -152,6 +156,7 @@ class GitHubProvider extends Provider { } const identityId = await this.getIdentityId(tokenData); await this.putToken(identityId, tokenData); + this.logger.info('Completed authentication with GitHub'); return identityId; } diff --git a/src/identities/types.ts b/src/identities/types.ts index d2ee7bd89..d567ed8a4 100644 --- a/src/identities/types.ts +++ b/src/identities/types.ts @@ -1,4 +1,4 @@ -import type { Opaque } from '../types'; +import type { Opaque, POJO } from '../types'; import type { Claim } from '../claims/types'; /** @@ -55,8 +55,6 @@ type IdentityInfo = IdentityData & { claims: IdentityClaims; }; -type ProviderTokens = Record; - type TokenData = { accessToken: string; refreshToken?: string; @@ -64,6 +62,13 @@ type TokenData = { refreshTokenExpiresIn?: number; }; +type ProviderTokens = Record; + +type ProviderAuthenticateRequest = { + url: string; + data: POJO; +}; + export type { ProviderId, IdentityId, @@ -74,4 +79,5 @@ export type { IdentityInfo, TokenData, ProviderTokens, + ProviderAuthenticateRequest, }; 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 152afa5b2..e666e9903 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 @@ -433,14 +433,14 @@ interface IClientServiceService_IVaultsLog extends grpc.MethodDefinition; responseDeserialize: grpc.deserialize; } -interface IClientServiceService_IIdentitiesAuthenticate extends grpc.MethodDefinition { +interface IClientServiceService_IIdentitiesAuthenticate extends grpc.MethodDefinition { path: "/polykey.v1.ClientService/IdentitiesAuthenticate"; requestStream: false; responseStream: true; requestSerialize: grpc.serialize; requestDeserialize: grpc.deserialize; - responseSerialize: grpc.serialize; - responseDeserialize: grpc.deserialize; + responseSerialize: grpc.serialize; + responseDeserialize: grpc.deserialize; } interface IClientServiceService_IIdentitiesTokenPut extends grpc.MethodDefinition { path: "/polykey.v1.ClientService/IdentitiesTokenPut"; @@ -674,7 +674,7 @@ export interface IClientServiceServer extends grpc.UntypedServiceImplementation vaultsPermissions: grpc.handleServerStreamingCall; vaultsVersion: grpc.handleUnaryCall; vaultsLog: grpc.handleServerStreamingCall; - identitiesAuthenticate: grpc.handleServerStreamingCall; + identitiesAuthenticate: grpc.handleServerStreamingCall; identitiesTokenPut: grpc.handleUnaryCall; identitiesTokenGet: grpc.handleUnaryCall; identitiesTokenDelete: grpc.handleUnaryCall; @@ -810,8 +810,8 @@ export interface IClientServiceClient { vaultsVersion(request: polykey_v1_vaults_vaults_pb.Version, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_vaults_vaults_pb.VersionResult) => void): grpc.ClientUnaryCall; vaultsLog(request: polykey_v1_vaults_vaults_pb.Log, options?: Partial): grpc.ClientReadableStream; 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; + 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; 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; @@ -988,8 +988,8 @@ export class ClientServiceClient extends grpc.Client implements IClientServiceCl public vaultsVersion(request: polykey_v1_vaults_vaults_pb.Version, metadata: grpc.Metadata, options: Partial, callback: (error: grpc.ServiceError | null, response: polykey_v1_vaults_vaults_pb.VersionResult) => void): grpc.ClientUnaryCall; public vaultsLog(request: polykey_v1_vaults_vaults_pb.Log, options?: Partial): grpc.ClientReadableStream; 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 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 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; 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 397a11162..28e446dc1 100644 --- a/src/proto/js/polykey/v1/client_service_grpc_pb.js +++ b/src/proto/js/polykey/v1/client_service_grpc_pb.js @@ -47,6 +47,17 @@ function deserialize_polykey_v1_gestalts_Graph(buffer_arg) { return polykey_v1_gestalts_gestalts_pb.Graph.deserializeBinary(new Uint8Array(buffer_arg)); } +function serialize_polykey_v1_identities_AuthenticationProcess(arg) { + if (!(arg instanceof polykey_v1_identities_identities_pb.AuthenticationProcess)) { + throw new Error('Expected argument of type polykey.v1.identities.AuthenticationProcess'); + } + return Buffer.from(arg.serializeBinary()); +} + +function deserialize_polykey_v1_identities_AuthenticationProcess(buffer_arg) { + return polykey_v1_identities_identities_pb.AuthenticationProcess.deserializeBinary(new Uint8Array(buffer_arg)); +} + function serialize_polykey_v1_identities_Info(arg) { if (!(arg instanceof polykey_v1_identities_identities_pb.Info)) { throw new Error('Expected argument of type polykey.v1.identities.Info'); @@ -907,11 +918,11 @@ identitiesAuthenticate: { requestStream: false, responseStream: true, requestType: polykey_v1_identities_identities_pb.Provider, - responseType: polykey_v1_identities_identities_pb.Provider, + responseType: polykey_v1_identities_identities_pb.AuthenticationProcess, requestSerialize: serialize_polykey_v1_identities_Provider, requestDeserialize: deserialize_polykey_v1_identities_Provider, - responseSerialize: serialize_polykey_v1_identities_Provider, - responseDeserialize: deserialize_polykey_v1_identities_Provider, + responseSerialize: serialize_polykey_v1_identities_AuthenticationProcess, + responseDeserialize: deserialize_polykey_v1_identities_AuthenticationProcess, }, identitiesTokenPut: { path: '/polykey.v1.ClientService/IdentitiesTokenPut', 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 19525d464..1ba7d80b6 100644 --- a/src/proto/js/polykey/v1/identities/identities_pb.d.ts +++ b/src/proto/js/polykey/v1/identities/identities_pb.d.ts @@ -9,8 +9,8 @@ import * as jspb from "google-protobuf"; export class Provider extends jspb.Message { getProviderId(): string; setProviderId(value: string): Provider; - getMessage(): string; - setMessage(value: string): Provider; + getIdentityId(): string; + setIdentityId(value: string): Provider; serializeBinary(): Uint8Array; toObject(includeInstance?: boolean): Provider.AsObject; @@ -25,7 +25,7 @@ export class Provider extends jspb.Message { export namespace Provider { export type AsObject = { providerId: string, - message: string, + identityId: string, } } @@ -75,6 +75,89 @@ export namespace Token { } } +export class AuthenticationProcess extends jspb.Message { + + hasRequest(): boolean; + clearRequest(): void; + getRequest(): AuthenticationRequest | undefined; + setRequest(value?: AuthenticationRequest): AuthenticationProcess; + + hasResponse(): boolean; + clearResponse(): void; + getResponse(): AuthenticationResponse | undefined; + setResponse(value?: AuthenticationResponse): AuthenticationProcess; + + getStepCase(): AuthenticationProcess.StepCase; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): AuthenticationProcess.AsObject; + static toObject(includeInstance: boolean, msg: AuthenticationProcess): AuthenticationProcess.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: AuthenticationProcess, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): AuthenticationProcess; + static deserializeBinaryFromReader(message: AuthenticationProcess, reader: jspb.BinaryReader): AuthenticationProcess; +} + +export namespace AuthenticationProcess { + export type AsObject = { + request?: AuthenticationRequest.AsObject, + response?: AuthenticationResponse.AsObject, + } + + export enum StepCase { + STEP_NOT_SET = 0, + REQUEST = 1, + RESPONSE = 2, + } + +} + +export class AuthenticationRequest extends jspb.Message { + getUrl(): string; + setUrl(value: string): AuthenticationRequest; + + getDataMap(): jspb.Map; + clearDataMap(): void; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): AuthenticationRequest.AsObject; + static toObject(includeInstance: boolean, msg: AuthenticationRequest): AuthenticationRequest.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: AuthenticationRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): AuthenticationRequest; + static deserializeBinaryFromReader(message: AuthenticationRequest, reader: jspb.BinaryReader): AuthenticationRequest; +} + +export namespace AuthenticationRequest { + export type AsObject = { + url: string, + + dataMap: Array<[string, string]>, + } +} + +export class AuthenticationResponse extends jspb.Message { + getIdentityId(): string; + setIdentityId(value: string): AuthenticationResponse; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): AuthenticationResponse.AsObject; + static toObject(includeInstance: boolean, msg: AuthenticationResponse): AuthenticationResponse.AsObject; + static extensions: {[key: number]: jspb.ExtensionFieldInfo}; + static extensionsBinary: {[key: number]: jspb.ExtensionFieldBinaryInfo}; + static serializeBinaryToWriter(message: AuthenticationResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): AuthenticationResponse; + static deserializeBinaryFromReader(message: AuthenticationResponse, reader: jspb.BinaryReader): AuthenticationResponse; +} + +export namespace AuthenticationResponse { + export type AsObject = { + identityId: string, + } +} + export class ProviderSearch extends jspb.Message { hasProvider(): boolean; diff --git a/src/proto/js/polykey/v1/identities/identities_pb.js b/src/proto/js/polykey/v1/identities/identities_pb.js index 174ca65aa..a42e1ce0a 100644 --- a/src/proto/js/polykey/v1/identities/identities_pb.js +++ b/src/proto/js/polykey/v1/identities/identities_pb.js @@ -14,6 +14,10 @@ var jspb = require('google-protobuf'); var goog = jspb; var global = Function('return this')(); +goog.exportSymbol('proto.polykey.v1.identities.AuthenticationProcess', null, global); +goog.exportSymbol('proto.polykey.v1.identities.AuthenticationProcess.StepCase', null, global); +goog.exportSymbol('proto.polykey.v1.identities.AuthenticationRequest', null, global); +goog.exportSymbol('proto.polykey.v1.identities.AuthenticationResponse', null, global); goog.exportSymbol('proto.polykey.v1.identities.Info', null, global); goog.exportSymbol('proto.polykey.v1.identities.Provider', null, global); goog.exportSymbol('proto.polykey.v1.identities.ProviderSearch', null, global); @@ -82,6 +86,69 @@ 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.AuthenticationProcess = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, proto.polykey.v1.identities.AuthenticationProcess.oneofGroups_); +}; +goog.inherits(proto.polykey.v1.identities.AuthenticationProcess, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.polykey.v1.identities.AuthenticationProcess.displayName = 'proto.polykey.v1.identities.AuthenticationProcess'; +} +/** + * 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.AuthenticationRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.polykey.v1.identities.AuthenticationRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.polykey.v1.identities.AuthenticationRequest.displayName = 'proto.polykey.v1.identities.AuthenticationRequest'; +} +/** + * 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.AuthenticationResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.polykey.v1.identities.AuthenticationResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.polykey.v1.identities.AuthenticationResponse.displayName = 'proto.polykey.v1.identities.AuthenticationResponse'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -157,7 +224,7 @@ proto.polykey.v1.identities.Provider.prototype.toObject = function(opt_includeIn proto.polykey.v1.identities.Provider.toObject = function(includeInstance, msg) { var f, obj = { providerId: jspb.Message.getFieldWithDefault(msg, 1, ""), - message: jspb.Message.getFieldWithDefault(msg, 2, "") + identityId: jspb.Message.getFieldWithDefault(msg, 2, "") }; if (includeInstance) { @@ -200,7 +267,7 @@ proto.polykey.v1.identities.Provider.deserializeBinaryFromReader = function(msg, break; case 2: var value = /** @type {string} */ (reader.readString()); - msg.setMessage(value); + msg.setIdentityId(value); break; default: reader.skipField(); @@ -238,7 +305,7 @@ proto.polykey.v1.identities.Provider.serializeBinaryToWriter = function(message, f ); } - f = message.getMessage(); + f = message.getIdentityId(); if (f.length > 0) { writer.writeString( 2, @@ -267,10 +334,10 @@ proto.polykey.v1.identities.Provider.prototype.setProviderId = function(value) { /** - * optional string message = 2; + * optional string identity_id = 2; * @return {string} */ -proto.polykey.v1.identities.Provider.prototype.getMessage = function() { +proto.polykey.v1.identities.Provider.prototype.getIdentityId = function() { return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); }; @@ -279,7 +346,7 @@ proto.polykey.v1.identities.Provider.prototype.getMessage = function() { * @param {string} value * @return {!proto.polykey.v1.identities.Provider} returns this */ -proto.polykey.v1.identities.Provider.prototype.setMessage = function(value) { +proto.polykey.v1.identities.Provider.prototype.setIdentityId = function(value) { return jspb.Message.setProto3StringField(this, 2, value); }; @@ -596,6 +663,527 @@ proto.polykey.v1.identities.Token.prototype.setToken = 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.AuthenticationProcess.oneofGroups_ = [[1,2]]; + +/** + * @enum {number} + */ +proto.polykey.v1.identities.AuthenticationProcess.StepCase = { + STEP_NOT_SET: 0, + REQUEST: 1, + RESPONSE: 2 +}; + +/** + * @return {proto.polykey.v1.identities.AuthenticationProcess.StepCase} + */ +proto.polykey.v1.identities.AuthenticationProcess.prototype.getStepCase = function() { + return /** @type {proto.polykey.v1.identities.AuthenticationProcess.StepCase} */(jspb.Message.computeOneofCase(this, proto.polykey.v1.identities.AuthenticationProcess.oneofGroups_[0])); +}; + + + +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.AuthenticationProcess.prototype.toObject = function(opt_includeInstance) { + return proto.polykey.v1.identities.AuthenticationProcess.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.AuthenticationProcess} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.identities.AuthenticationProcess.toObject = function(includeInstance, msg) { + var f, obj = { + request: (f = msg.getRequest()) && proto.polykey.v1.identities.AuthenticationRequest.toObject(includeInstance, f), + response: (f = msg.getResponse()) && proto.polykey.v1.identities.AuthenticationResponse.toObject(includeInstance, f) + }; + + 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.AuthenticationProcess} + */ +proto.polykey.v1.identities.AuthenticationProcess.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.polykey.v1.identities.AuthenticationProcess; + return proto.polykey.v1.identities.AuthenticationProcess.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.polykey.v1.identities.AuthenticationProcess} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.polykey.v1.identities.AuthenticationProcess} + */ +proto.polykey.v1.identities.AuthenticationProcess.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.AuthenticationRequest; + reader.readMessage(value,proto.polykey.v1.identities.AuthenticationRequest.deserializeBinaryFromReader); + msg.setRequest(value); + break; + case 2: + var value = new proto.polykey.v1.identities.AuthenticationResponse; + reader.readMessage(value,proto.polykey.v1.identities.AuthenticationResponse.deserializeBinaryFromReader); + msg.setResponse(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.polykey.v1.identities.AuthenticationProcess.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.polykey.v1.identities.AuthenticationProcess.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.AuthenticationProcess} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.identities.AuthenticationProcess.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getRequest(); + if (f != null) { + writer.writeMessage( + 1, + f, + proto.polykey.v1.identities.AuthenticationRequest.serializeBinaryToWriter + ); + } + f = message.getResponse(); + if (f != null) { + writer.writeMessage( + 2, + f, + proto.polykey.v1.identities.AuthenticationResponse.serializeBinaryToWriter + ); + } +}; + + +/** + * optional AuthenticationRequest request = 1; + * @return {?proto.polykey.v1.identities.AuthenticationRequest} + */ +proto.polykey.v1.identities.AuthenticationProcess.prototype.getRequest = function() { + return /** @type{?proto.polykey.v1.identities.AuthenticationRequest} */ ( + jspb.Message.getWrapperField(this, proto.polykey.v1.identities.AuthenticationRequest, 1)); +}; + + +/** + * @param {?proto.polykey.v1.identities.AuthenticationRequest|undefined} value + * @return {!proto.polykey.v1.identities.AuthenticationProcess} returns this +*/ +proto.polykey.v1.identities.AuthenticationProcess.prototype.setRequest = function(value) { + return jspb.Message.setOneofWrapperField(this, 1, proto.polykey.v1.identities.AuthenticationProcess.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.polykey.v1.identities.AuthenticationProcess} returns this + */ +proto.polykey.v1.identities.AuthenticationProcess.prototype.clearRequest = function() { + return this.setRequest(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.polykey.v1.identities.AuthenticationProcess.prototype.hasRequest = function() { + return jspb.Message.getField(this, 1) != null; +}; + + +/** + * optional AuthenticationResponse response = 2; + * @return {?proto.polykey.v1.identities.AuthenticationResponse} + */ +proto.polykey.v1.identities.AuthenticationProcess.prototype.getResponse = function() { + return /** @type{?proto.polykey.v1.identities.AuthenticationResponse} */ ( + jspb.Message.getWrapperField(this, proto.polykey.v1.identities.AuthenticationResponse, 2)); +}; + + +/** + * @param {?proto.polykey.v1.identities.AuthenticationResponse|undefined} value + * @return {!proto.polykey.v1.identities.AuthenticationProcess} returns this +*/ +proto.polykey.v1.identities.AuthenticationProcess.prototype.setResponse = function(value) { + return jspb.Message.setOneofWrapperField(this, 2, proto.polykey.v1.identities.AuthenticationProcess.oneofGroups_[0], value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.polykey.v1.identities.AuthenticationProcess} returns this + */ +proto.polykey.v1.identities.AuthenticationProcess.prototype.clearResponse = function() { + return this.setResponse(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.polykey.v1.identities.AuthenticationProcess.prototype.hasResponse = function() { + return jspb.Message.getField(this, 2) != null; +}; + + + + + +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.AuthenticationRequest.prototype.toObject = function(opt_includeInstance) { + return proto.polykey.v1.identities.AuthenticationRequest.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.AuthenticationRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.identities.AuthenticationRequest.toObject = function(includeInstance, msg) { + var f, obj = { + url: jspb.Message.getFieldWithDefault(msg, 1, ""), + dataMap: (f = msg.getDataMap()) ? f.toObject(includeInstance, undefined) : [] + }; + + 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.AuthenticationRequest} + */ +proto.polykey.v1.identities.AuthenticationRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.polykey.v1.identities.AuthenticationRequest; + return proto.polykey.v1.identities.AuthenticationRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.polykey.v1.identities.AuthenticationRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.polykey.v1.identities.AuthenticationRequest} + */ +proto.polykey.v1.identities.AuthenticationRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setUrl(value); + break; + case 2: + var value = msg.getDataMap(); + reader.readMessage(value, function(message, reader) { + jspb.Map.deserializeBinary(message, reader, jspb.BinaryReader.prototype.readString, jspb.BinaryReader.prototype.readString, null, "", ""); + }); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.polykey.v1.identities.AuthenticationRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.polykey.v1.identities.AuthenticationRequest.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.AuthenticationRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.identities.AuthenticationRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getUrl(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getDataMap(true); + if (f && f.getLength() > 0) { + f.serializeBinary(2, writer, jspb.BinaryWriter.prototype.writeString, jspb.BinaryWriter.prototype.writeString); + } +}; + + +/** + * optional string url = 1; + * @return {string} + */ +proto.polykey.v1.identities.AuthenticationRequest.prototype.getUrl = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.polykey.v1.identities.AuthenticationRequest} returns this + */ +proto.polykey.v1.identities.AuthenticationRequest.prototype.setUrl = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * map data = 2; + * @param {boolean=} opt_noLazyCreate Do not create the map if + * empty, instead returning `undefined` + * @return {!jspb.Map} + */ +proto.polykey.v1.identities.AuthenticationRequest.prototype.getDataMap = function(opt_noLazyCreate) { + return /** @type {!jspb.Map} */ ( + jspb.Message.getMapField(this, 2, opt_noLazyCreate, + null)); +}; + + +/** + * Clears values from the map. The map will be non-null. + * @return {!proto.polykey.v1.identities.AuthenticationRequest} returns this + */ +proto.polykey.v1.identities.AuthenticationRequest.prototype.clearDataMap = function() { + this.getDataMap().clear(); + return this;}; + + + + + +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.AuthenticationResponse.prototype.toObject = function(opt_includeInstance) { + return proto.polykey.v1.identities.AuthenticationResponse.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.AuthenticationResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.identities.AuthenticationResponse.toObject = function(includeInstance, msg) { + var f, obj = { + identityId: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + 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.AuthenticationResponse} + */ +proto.polykey.v1.identities.AuthenticationResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.polykey.v1.identities.AuthenticationResponse; + return proto.polykey.v1.identities.AuthenticationResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.polykey.v1.identities.AuthenticationResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.polykey.v1.identities.AuthenticationResponse} + */ +proto.polykey.v1.identities.AuthenticationResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setIdentityId(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.polykey.v1.identities.AuthenticationResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.polykey.v1.identities.AuthenticationResponse.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.AuthenticationResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.polykey.v1.identities.AuthenticationResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getIdentityId(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string identity_id = 1; + * @return {string} + */ +proto.polykey.v1.identities.AuthenticationResponse.prototype.getIdentityId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.polykey.v1.identities.AuthenticationResponse} returns this + */ +proto.polykey.v1.identities.AuthenticationResponse.prototype.setIdentityId = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + + /** * List of repeated fields within this message type. * @private {!Array} diff --git a/src/proto/schemas/polykey/v1/client_service.proto b/src/proto/schemas/polykey/v1/client_service.proto index d27cd8562..2f5d384fa 100644 --- a/src/proto/schemas/polykey/v1/client_service.proto +++ b/src/proto/schemas/polykey/v1/client_service.proto @@ -65,7 +65,7 @@ service ClientService { rpc VaultsLog(polykey.v1.vaults.Log) returns (stream polykey.v1.vaults.LogEntry); // Identities - rpc IdentitiesAuthenticate(polykey.v1.identities.Provider) returns (stream polykey.v1.identities.Provider); + rpc IdentitiesAuthenticate(polykey.v1.identities.Provider) returns (stream polykey.v1.identities.AuthenticationProcess); 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); diff --git a/src/proto/schemas/polykey/v1/identities/identities.proto b/src/proto/schemas/polykey/v1/identities/identities.proto index b45a27fc7..35e23b9fe 100644 --- a/src/proto/schemas/polykey/v1/identities/identities.proto +++ b/src/proto/schemas/polykey/v1/identities/identities.proto @@ -4,7 +4,7 @@ package polykey.v1.identities; message Provider { string provider_id = 1; - string message = 2; + string identity_id = 2; } message TokenSpecific { @@ -16,6 +16,22 @@ message Token { string token = 1; } +message AuthenticationProcess { + oneof step { + AuthenticationRequest request = 1; + AuthenticationResponse response = 2; + } +} + +message AuthenticationRequest { + string url = 1; + map data = 2; +} + +message AuthenticationResponse { + string identity_id = 1; +} + message ProviderSearch { Provider provider = 1; repeated string search_term = 2; diff --git a/src/sigchain/Sigchain.ts b/src/sigchain/Sigchain.ts index e7627a3b5..0d179082e 100644 --- a/src/sigchain/Sigchain.ts +++ b/src/sigchain/Sigchain.ts @@ -220,8 +220,8 @@ class Sigchain { * Appends a claim (of any type) to the sigchain. */ @ready(new sigchainErrors.ErrorSigchainNotRunning()) - public async addClaim(claimData: ClaimData): Promise { - await this._transaction(async () => { + public async addClaim(claimData: ClaimData): Promise { + return await this._transaction(async () => { const prevSequenceNumber = await this.getSequenceNumber(); const newSequenceNumber = prevSequenceNumber + 1; @@ -247,6 +247,7 @@ class Sigchain { }, ]; await this.db.batch(ops); + return claim; }); } diff --git a/tests/bin/identities.test.ts b/tests/bin/identities.test.ts index 3a46523fe..042c7039f 100644 --- a/tests/bin/identities.test.ts +++ b/tests/bin/identities.test.ts @@ -11,6 +11,8 @@ import fs from 'fs'; import Logger, { LogLevel, StreamHandler } from '@matrixai/logger'; import { PolykeyAgent } from '@'; import { makeNodeId } from '@/nodes/utils'; +import * as claimsUtils from '@/claims/utils'; +import * as identitiesUtils from '@/identities/utils'; import * as testUtils from './utils'; import * as utils from './utils'; import { @@ -33,19 +35,6 @@ function identityString( return `${providerId}:${identityId}`; } -/** - * This test file has been optimised to use only one instance of PolykeyAgent where posible. - * Setting up the PolykeyAgent has been done in a beforeAll block. - * Keep this in mind when adding or editing tests. - * Any side effects need to be undone when the test has completed. - * Preferably within a `afterEach()` since any cleanup will be skipped inside a failing test. - * - * - left over state can cause a test to fail in certain cases. - * - left over state can cause similar tests to succeed when they should fail. - * - starting or stopping the agent within tests should be done on a new instance of the polykey agent. - * - when in doubt test each modified or added test on it's own as well as the whole file. - * - Looking into adding a way to safely clear each domain's DB information with out breaking modules. - */ describe('CLI Identities', () => { const password = 'password'; // Test dependent variables @@ -54,6 +43,7 @@ describe('CLI Identities', () => { let passwordFile: string; let polykeyAgent: PolykeyAgent; let testProvider: TestProvider; + let mockedBrowser: jest.SpyInstance; // Defining constants const nodeId1 = makeNodeId( @@ -135,7 +125,11 @@ describe('CLI Identities', () => { keynode.id = polykeyAgent.nodeManager.getNodeId(); testProvider = new TestProvider(); - await polykeyAgent.identitiesManager.registerProvider(testProvider); + polykeyAgent.identitiesManager.registerProvider(testProvider); + + mockedBrowser = jest + .spyOn(identitiesUtils, 'browser') + .mockImplementation(() => {}); // Authorize session await utils.pkStdio( @@ -147,6 +141,7 @@ describe('CLI Identities', () => { afterAll(async () => { await polykeyAgent.stop(); await polykeyAgent.destroy(); + mockedBrowser.mockRestore(); await fs.promises.rmdir(dataDir, { recursive: true }); }); beforeEach(async () => { @@ -191,16 +186,6 @@ describe('CLI Identities', () => { const command3 = genCommands(['allow', node1.id, 'invalid']); const result3 = await testUtils.pkStdio(command3, {}, dataDir); expect(result3.exitCode).toBe(1); // Should fail. - - // Cleaning up changes to state. - await polykeyAgent.gestaltGraph.unsetGestaltActionByNode( - node1.id, - 'notify', - ); - await polykeyAgent.gestaltGraph.unsetGestaltActionByNode( - node1.id, - 'scan', - ); }); test('Should allow permissions on Identity.', async () => { const commands = genCommands([ @@ -244,18 +229,6 @@ describe('CLI Identities', () => { ]); const result3 = await testUtils.pkStdio(command3, {}, dataDir); expect(result3.exitCode).toBe(1); // Should fail. - - // Cleaning up changes to state. - await polykeyAgent.gestaltGraph.unsetGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'notify', - ); - await polykeyAgent.gestaltGraph.unsetGestaltActionByIdentity( - identity1.providerId, - identity1.identityId, - 'scan', - ); }); test('Should fail on invalid inputs.', async () => { let result; @@ -542,8 +515,14 @@ describe('CLI Identities', () => { expect(result.exitCode === 0).toBeFalsy(); // Fails.. }); }); - describe('commandClaimKeynode', () => { - test('Should claim a keynode.', async () => { + describe('commandClaimIdentity', () => { + test('Should claim an identity.', async () => { + // Need an authenticated identity. + await polykeyAgent.identitiesManager.putToken( + testToken.providerId, + testToken.identityId, + testToken.tokenData, + ); const commands = [ 'identities', 'claim', @@ -554,15 +533,26 @@ describe('CLI Identities', () => { ]; const result = await testUtils.pkStdio(commands, {}, dataDir); expect(result.exitCode).toBe(0); // Succeeds. - - const gestalt = await polykeyAgent.gestaltGraph.getGestaltByIdentity( + // Unauthenticate identity + await polykeyAgent.identitiesManager.delToken( testToken.providerId, testToken.identityId, ); - const gestaltString = JSON.stringify(gestalt); - expect(gestaltString).toContain(testToken.providerId); - expect(gestaltString).toContain(testToken.identityId); - expect(gestaltString).toContain(keynode.id); + // Unclaim identity + testProvider.links = {}; + testProvider.linkIdCounter = 0; + }); + test('Should fail for unauthenticated identities.', async () => { + const commands = [ + 'identities', + 'claim', + '-np', + nodePath, + testToken.providerId, + testToken.identityId, + ]; + const result = await testUtils.pkStdio(commands, {}, dataDir); + expect(result.exitCode === 0).toBeFalsy(); // Fails.. }); }); describe('commandAuthenticateProvider', () => { @@ -579,7 +569,11 @@ describe('CLI Identities', () => { const result = await testUtils.pkStdio(commands, {}, dataDir); expect(result.exitCode).toBe(0); // Succeeds. expect(result.stdout).toContain('randomtestcode'); - expect(result.stdout).toContain('test_user'); + // Unauthenticate identity + await polykeyAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); }); }); describe('commandGetGestalts', () => { @@ -686,6 +680,7 @@ describe('CLI Identities', () => { await addRemoteDetails(nodeB, polykeyAgent); await addRemoteDetails(nodeB, nodeC); await addRemoteDetails(nodeC, nodeB); + // Adding sigchain details. const claimBtoC: ClaimLinkNode = { type: 'node', @@ -703,7 +698,7 @@ describe('CLI Identities', () => { await nodeC.sigchain.addClaim(claimBtoC); // Setting up identtiy. - const gen = await testProvider.authenticate(); + const gen = testProvider.authenticate(); await gen.next(); identityId = (await gen.next()).value as IdentityId; @@ -713,12 +708,17 @@ describe('CLI Identities', () => { provider: testProvider.id, identity: identityId, }; - await nodeB.sigchain.addClaim(claimIdentToB); + const claimEncoded = await nodeB.sigchain.addClaim(claimIdentToB); + const claim = claimsUtils.decodeClaim(claimEncoded); + await testProvider.publishClaim(identityId, claim); }, global.polykeyStartupTimeout * 2); afterAll(async () => { // Clean up the remote gestalt state here. await cleanupRemoteKeynode(nodeB); await cleanupRemoteKeynode(nodeC); + // Unclaim identity + testProvider.links = {}; + testProvider.linkIdCounter = 0; }); afterEach(async () => { // Clean the local nodes gestalt graph here. @@ -727,6 +727,13 @@ describe('CLI Identities', () => { await nodeC.gestaltGraph.clearDB(); }); test('Should start discovery by Node', async () => { + // Authenticate identity + await polykeyAgent.identitiesManager.putToken( + testToken.providerId, + identityId, + testToken.tokenData, + ); + const commands = [ 'identities', 'discover', @@ -744,11 +751,18 @@ describe('CLI Identities', () => { expect(gestaltString).toContain(nodeB.nodeManager.getNodeId()); expect(gestaltString).toContain(nodeC.nodeManager.getNodeId()); expect(gestaltString).toContain(identityId); + // Unauthenticate identity + await polykeyAgent.identitiesManager.delToken( + testToken.providerId, + identityId, + ); }); test('Should start discovery by Identity', async () => { - await testProvider.overrideLinks( - nodeB.nodeManager.getNodeId(), - nodeB.keyManager.getRootKeyPairPem().privateKey, + // Authenticate identity + await polykeyAgent.identitiesManager.putToken( + testToken.providerId, + identityId, + testToken.tokenData, ); const commands = [ @@ -758,8 +772,8 @@ describe('CLI Identities', () => { nodePath, identityString(testProvider.id, identityId), ]; - const result = await testUtils.pk(commands); - expect(result).toBe(0); + const result = await testUtils.pkStdio(commands, {}, dataDir); + expect(result.exitCode).toBe(0); // We expect to find a gestalt now. const gestalt = await polykeyAgent.gestaltGraph.getGestalts(); @@ -768,6 +782,11 @@ describe('CLI Identities', () => { expect(gestaltString).toContain(nodeB.nodeManager.getNodeId()); expect(gestaltString).toContain(nodeC.nodeManager.getNodeId()); expect(gestaltString).toContain(identityId); + // Unauthenticate identity + await polykeyAgent.identitiesManager.delToken( + testToken.providerId, + identityId, + ); }); }); }); diff --git a/tests/client/rpcGestalts.test.ts b/tests/client/rpcGestalts.test.ts index c1408658c..3fd020e88 100644 --- a/tests/client/rpcGestalts.test.ts +++ b/tests/client/rpcGestalts.test.ts @@ -245,7 +245,7 @@ describe('Client service', () => { // Testing the call const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(identity1.providerId); - providerMessage.setMessage(identity1.identityId); + providerMessage.setIdentityId(identity1.identityId); const res = await gestaltsGetIdentity(providerMessage, callCredentials); const jsonString = res.getGestaltGraph(); @@ -282,7 +282,7 @@ describe('Client service', () => { const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(testToken.providerId); - providerMessage.setMessage(testToken.identityId); + providerMessage.setIdentityId(testToken.identityId); // Technically contains a node, but no other thing, will succeed with no results expect( await gestaltsDiscoverIdentity(providerMessage, callCredentials), @@ -336,7 +336,7 @@ describe('Client service', () => { const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(identity1.providerId); - providerMessage.setMessage(identity1.identityId); + providerMessage.setIdentityId(identity1.identityId); // Should have permissions scan and notify as above const test1 = await gestaltsGetActionsByIdentity( providerMessage, @@ -347,7 +347,7 @@ describe('Client service', () => { expect(test1.getActionList().includes('notify')).toBeTruthy(); providerMessage.setProviderId(identity1.providerId); - providerMessage.setMessage('Not a real identity'); + providerMessage.setIdentityId('Not a real identity'); // Should have no permissions const test2 = await gestaltsGetActionsByIdentity( providerMessage, @@ -393,7 +393,7 @@ describe('Client service', () => { const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(identity1.providerId); - providerMessage.setMessage(identity1.identityId); + providerMessage.setIdentityId(identity1.identityId); const setActionsMessage = new permissionsPB.ActionSet(); setActionsMessage.setIdentity(providerMessage); @@ -470,7 +470,7 @@ describe('Client service', () => { const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(identity1.providerId); - providerMessage.setMessage(identity1.identityId); + providerMessage.setIdentityId(identity1.identityId); const setActionsMessage = new permissionsPB.ActionSet(); setActionsMessage.setIdentity(providerMessage); diff --git a/tests/client/rpcIdentities.test.ts b/tests/client/rpcIdentities.test.ts index 3309dfdf4..47da0d94c 100644 --- a/tests/client/rpcIdentities.test.ts +++ b/tests/client/rpcIdentities.test.ts @@ -1,7 +1,5 @@ import type * as grpc from '@grpc/grpc-js'; import type { IdentitiesManager } from '@/identities'; -import type { GestaltGraph } from '@/gestalts'; -import type { NodeManager } from '@/nodes'; import type { IdentityId, ProviderId } from '@/identities/types'; import type { ClientServiceClient } from '@/proto/js/polykey/v1/client_service_grpc_pb'; import os from 'os'; @@ -48,12 +46,11 @@ describe('Identities Client service', () => { let dataDir: string; let polykeyAgent: PolykeyAgent; let keyManager: KeyManager; - let nodeManager: NodeManager; - let gestaltGraph: GestaltGraph; let identitiesManager: IdentitiesManager; let passwordFile: string; let callCredentials: grpc.Metadata; + let testProvider: TestProvider; const testToken = { providerId: 'test-provider' as ProviderId, identityId: 'test_user' as IdentityId, @@ -90,12 +87,10 @@ describe('Identities Client service', () => { keyManager, }); - nodeManager = polykeyAgent.nodeManager; - gestaltGraph = polykeyAgent.gestaltGraph; identitiesManager = polykeyAgent.identitiesManager; // Adding provider. - const testProvider = new TestProvider(); + testProvider = new TestProvider(); identitiesManager.registerProvider(testProvider); [server, port] = await testUtils.openTestClientServer({ @@ -125,30 +120,49 @@ describe('Identities Client service', () => { test('should authenticate an identity', async () => { const identitiesAuthenticate = - grpcUtils.promisifyReadableStreamCall( + grpcUtils.promisifyReadableStreamCall( client, client.identitiesAuthenticate, ); const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(testToken.providerId); - providerMessage.setMessage(testToken.identityId); + providerMessage.setIdentityId(testToken.identityId); - const gen = identitiesAuthenticate(providerMessage, callCredentials); - - const firstMessage = await gen.next(); - expect(firstMessage.done).toBeFalsy(); - expect(firstMessage.value).toBeTruthy(); - if (!firstMessage.value) fail('Failed to return a message'); - expect(firstMessage.value.getMessage()).toContain('randomtestcode'); - - const secondMessage = await gen.next(); - expect(secondMessage.done).toBeFalsy(); - expect(secondMessage.value).toBeTruthy(); - if (!secondMessage.value) fail('Failed to return a message'); - expect(secondMessage.value.getMessage()).toContain('test_user'); - - expect((await gen.next()).done).toBeTruthy(); + const genReadable = identitiesAuthenticate( + providerMessage, + callCredentials, + ); + let step = 0; + for await (const message of genReadable) { + if (step === 0) { + expect(message.getStepCase()).toBe( + identitiesPB.AuthenticationProcess.StepCase.REQUEST, + ); + const authRequest = message.getRequest()!; + expect(authRequest.getUrl()).toBe('test.com'); + expect(authRequest.getDataMap().get('userCode')).toBe('randomtestcode'); + } + if (step === 1) { + expect(message.getStepCase()).toBe( + identitiesPB.AuthenticationProcess.StepCase.RESPONSE, + ); + const authResponse = message.getResponse()!; + expect(authResponse.getIdentityId()).toBe(testToken.identityId); + } + step++; + } + expect( + await polykeyAgent.identitiesManager.getToken( + testToken.providerId, + testToken.identityId, + ), + ).toEqual(testToken.tokenData); + expect(genReadable.stream.destroyed).toBeTruthy(); + await polykeyAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); }); test('should manipulate tokens for providers', async () => { const putToken = grpcUtils.promisifyUnaryCall( @@ -175,7 +189,7 @@ describe('Identities Client service', () => { const m = new identitiesPB.TokenSpecific(); mp.setProviderId(providerId); - mp.setMessage(identityId); + mp.setIdentityId(identityId); m.setProvider(mp); m.setToken('abc'); @@ -217,7 +231,7 @@ describe('Identities Client service', () => { const providerSearchMessage = new identitiesPB.ProviderSearch(); const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(testToken.providerId); - providerMessage.setMessage(testToken.identityId); + providerMessage.setIdentityId(testToken.identityId); providerSearchMessage.setProvider(providerMessage); providerSearchMessage.setSearchTermList([]); @@ -252,14 +266,13 @@ describe('Identities Client service', () => { callCredentials, ); expect(providerMessage.getProviderId()).toBe(testToken.providerId); - expect(providerMessage.getMessage()).toBe(testToken.identityId); + expect(providerMessage.getIdentityId()).toBe(testToken.identityId); }); - test('should augment a keynode.', async () => { - const identitiesAugmentKeynode = - grpcUtils.promisifyUnaryCall( - client, - client.identitiesClaim, - ); + test('should claim an identity.', async () => { + const identitiesClaim = grpcUtils.promisifyUnaryCall( + client, + client.identitiesClaim, + ); // Need an authenticated identity await identitiesManager.putToken( testToken.providerId, @@ -269,11 +282,24 @@ describe('Identities Client service', () => { const providerMessage = new identitiesPB.Provider(); providerMessage.setProviderId(testToken.providerId); - providerMessage.setMessage(testToken.identityId); - await identitiesAugmentKeynode(providerMessage, callCredentials); - const res = await gestaltGraph.getGestaltByNode(nodeManager.getNodeId()); - const resString = JSON.stringify(res); - expect(resString).toContain(testToken.providerId); - expect(resString).toContain(testToken.identityId); + providerMessage.setIdentityId(testToken.identityId); + await identitiesClaim(providerMessage, callCredentials); + + const claim = await ( + await testProvider + .getClaims(testToken.identityId, testToken.identityId) + .next() + ).value; + expect(claim.payload.data.type).toBe('identity'); + expect(claim.payload.data.provider).toBe(testToken.providerId); + expect(claim.payload.data.identity).toBe(testToken.identityId); + expect(claim.payload.data.node).toBe(polykeyAgent.nodeManager.getNodeId()); + + await polykeyAgent.identitiesManager.delToken( + testToken.providerId, + testToken.identityId, + ); + testProvider.links = {}; + testProvider.linkIdCounter = 0; }); }); diff --git a/tests/client/utils.ts b/tests/client/utils.ts index 6ca47d3af..7f7181daa 100644 --- a/tests/client/utils.ts +++ b/tests/client/utils.ts @@ -33,6 +33,7 @@ async function openTestClientServer({ sessionManager: polykeyAgent.sessionManager, notificationsManager: polykeyAgent.notificationsManager, discovery: polykeyAgent.discovery, + sigchain: polykeyAgent.sigchain, fwdProxy: polykeyAgent.fwdProxy, revProxy: polykeyAgent.revProxy, grpcServerClient: polykeyAgent.grpcServerClient, diff --git a/tests/discovery/Discovery.test.ts b/tests/discovery/Discovery.test.ts index e6d557371..f912635e3 100644 --- a/tests/discovery/Discovery.test.ts +++ b/tests/discovery/Discovery.test.ts @@ -1,5 +1,5 @@ import type { ClaimLinkIdentity, ClaimLinkNode } from '@/claims/types'; -import type { IdentityId } from '@/identities/types'; +import type { IdentityId, ProviderId } from '@/identities/types'; import fs from 'fs'; import path from 'path'; import os from 'os'; @@ -7,6 +7,7 @@ import Logger, { LogLevel } from '@matrixai/logger'; import { Discovery } from '@/discovery'; import PolykeyAgent from '@/PolykeyAgent'; import * as discoveryErrors from '@/discovery/errors'; +import * as claimsUtils from '@/claims/utils'; import TestProvider from '../identities/TestProvider'; import { addRemoteDetails, @@ -25,6 +26,13 @@ describe('Discovery', () => { // Constants. const password = 'password'; const logger = new Logger('Discovery Tests', LogLevel.WARN); + const testToken = { + providerId: 'test-provider' as ProviderId, + identityId: 'test_user' as IdentityId, + tokenData: { + accessToken: 'abc123', + }, + }; describe('Basic manager tests.', () => { // Managers @@ -115,7 +123,7 @@ describe('Discovery', () => { nodeA.identitiesManager.registerProvider(testProvider); // Setting up identtiy. - const gen = await testProvider.authenticate(); + const gen = testProvider.authenticate(); await gen.next(); identityId = (await gen.next()).value as IdentityId; @@ -125,12 +133,16 @@ describe('Discovery', () => { provider: testProvider.id, identity: identityId, }; - await nodeB.sigchain.addClaim(claimIdentToB); + const claimEncoded = await nodeB.sigchain.addClaim(claimIdentToB); + const claim = await claimsUtils.decodeClaim(claimEncoded); + await testProvider.publishClaim(identityId, claim); }, global.polykeyStartupTimeout * 3); afterAll(async () => { await cleanupRemoteKeynode(nodeA); await cleanupRemoteKeynode(nodeB); await cleanupRemoteKeynode(nodeC); + testProvider.links = {}; + testProvider.linkIdCounter = 0; }); beforeEach(async () => { await nodeA.gestaltGraph.clearDB(); @@ -155,10 +167,6 @@ describe('Discovery', () => { expect(gestaltString).toContain(identityId); }); test('discoverGestaltByNode by IdentityId', async () => { - await testProvider.overrideLinks( - nodeB.nodeManager.getNodeId(), - nodeB.keyManager.getRootKeyPairPem().privateKey, - ); // Time to do the test. const discoverProcess = nodeA.discovery.discoverGestaltByIdentity( testProvider.id, @@ -227,22 +235,6 @@ describe('Discovery', () => { const gen2 = testProvider.authenticate(); await gen2.next(); identityIdB = (await gen2.next()).value as IdentityId; - - const claimIdentToB: ClaimLinkIdentity = { - type: 'identity', - node: nodeB.nodeManager.getNodeId(), - provider: testProvider.id, - identity: identityIdA, - }; - await nodeB.sigchain.addClaim(claimIdentToB); - - const claimIdentToD: ClaimLinkIdentity = { - type: 'identity', - node: nodeD.nodeManager.getNodeId(), - provider: testProvider.id, - identity: identityIdB, - }; - await nodeD.sigchain.addClaim(claimIdentToD); }, global.polykeyStartupTimeout * 4); afterAll(async () => { await cleanupRemoteKeynode(nodeA); @@ -250,17 +242,31 @@ describe('Discovery', () => { await cleanupRemoteKeynode(nodeC); await cleanupRemoteKeynode(nodeD); }); - beforeEach(async () => { + afterEach(async () => { await nodeA.gestaltGraph.clearDB(); await nodeB.gestaltGraph.clearDB(); await nodeC.gestaltGraph.clearDB(); await nodeD.gestaltGraph.clearDB(); + testProvider.links = {}; + testProvider.linkIdCounter = 0; }); test('Gestalt1 discovers Gestalt2', async () => { - await testProvider.overrideLinks( - nodeD.nodeManager.getNodeId(), - nodeD.keyManager.getRootKeyPairPem().privateKey, + await nodeA.identitiesManager.putToken( + testToken.providerId, + identityIdA, + testToken.tokenData, ); + + const claimIdentToD: ClaimLinkIdentity = { + type: 'identity', + node: nodeD.nodeManager.getNodeId(), + provider: testProvider.id, + identity: identityIdB, + }; + const claimBEncoded = await nodeD.sigchain.addClaim(claimIdentToD); + const claimB = claimsUtils.decodeClaim(claimBEncoded); + await testProvider.publishClaim(identityIdB, claimB); + // Time to do the test. const discoverProcess = nodeA.discovery.discoverGestaltByIdentity( testProvider.id, @@ -277,12 +283,25 @@ describe('Discovery', () => { expect(gestaltString).toContain(nodeC.nodeManager.getNodeId()); expect(gestaltString).toContain(nodeD.nodeManager.getNodeId()); expect(gestaltString).toContain(identityIdB); + await nodeA.identitiesManager.delToken(testToken.providerId, identityIdA); }); test('Gestalt2 discovers Gestalt1', async () => { - await testProvider.overrideLinks( - nodeB.nodeManager.getNodeId(), - nodeB.keyManager.getRootKeyPairPem().privateKey, + await nodeC.identitiesManager.putToken( + testToken.providerId, + identityIdB, + testToken.tokenData, ); + + const claimIdentToB: ClaimLinkIdentity = { + type: 'identity', + node: nodeB.nodeManager.getNodeId(), + provider: testProvider.id, + identity: identityIdA, + }; + const claimAEncoded = await nodeB.sigchain.addClaim(claimIdentToB); + const claimA = claimsUtils.decodeClaim(claimAEncoded); + await testProvider.publishClaim(identityIdA, claimA); + // Time to do the test. const discoverProcess = nodeC.discovery.discoverGestaltByIdentity( testProvider.id, @@ -299,6 +318,7 @@ describe('Discovery', () => { expect(gestaltString).toContain(nodeA.nodeManager.getNodeId()); expect(gestaltString).toContain(nodeB.nodeManager.getNodeId()); expect(gestaltString).toContain(identityIdA); + await nodeC.identitiesManager.delToken(testToken.providerId, identityIdB); }); }); }); diff --git a/tests/identities/IdentitiesManager.test.ts b/tests/identities/IdentitiesManager.test.ts index 32778e583..cbc2aeb04 100644 --- a/tests/identities/IdentitiesManager.test.ts +++ b/tests/identities/IdentitiesManager.test.ts @@ -181,7 +181,7 @@ describe('IdentitiesManager', () => { const result1 = await authProcess.next(); // The test provider will provider a dummy authcode expect(result1.value).toBeDefined(); - expect(typeof result1.value).toBe('string'); + expect(typeof result1.value).toBe('object'); expect(result1.done).toBe(false); // This is when we have completed it const result2 = await authProcess.next(); diff --git a/tests/identities/TestProvider.ts b/tests/identities/TestProvider.ts index 79967b21c..e772c04bb 100644 --- a/tests/identities/TestProvider.ts +++ b/tests/identities/TestProvider.ts @@ -4,20 +4,19 @@ import type { IdentityId, TokenData, IdentityData, + ProviderAuthenticateRequest, } from '@/identities/types'; import type { Claim } from '@/claims/types'; import type { IdentityClaim, IdentityClaimId } from '@/identities/types'; -import type { NodeId } from '@/nodes/types'; import { Provider, errors as identitiesErrors } from '@/identities'; -import { createClaim, decodeClaim } from '@/claims/utils'; class TestProvider extends Provider { public readonly id = 'test-provider' as ProviderId; - protected linkIdCounter: number = 0; + public linkIdCounter: number = 0; protected users: Record; // FIXME: the string union on VaultId is to prevent some false errors. - protected links: 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, Array @@ -34,57 +33,25 @@ class TestProvider extends Provider { email: 'test_user2@test.com', }, }; - this.links = { - test_link: JSON.stringify({ - payload: { - hPrev: null, - seq: 1, - data: { - type: 'identity', - node: 'nodeId' as NodeId, - provider: this.id, - identity: 'test_user' as IdentityId, - }, - iat: 1618203162, - }, - signatures: { - nodeId: 'nodeidSignature', - test_user: 'test_userSignature', - }, - }), + this.userTokens = { + abc123: 'test_user' as IdentityId, }; + this.links = {}; this.userLinks = { test_user: ['test_link'], }; - this.userTokens = { - abc123: 'test_user' as IdentityId, - }; } - public async overrideLinks(nodeId: NodeId, privateKey: string) { - const claim = await createClaim({ + public async *authenticate(): AsyncGenerator< + ProviderAuthenticateRequest, + IdentityId + > { + yield { + url: 'test.com', data: { - type: 'identity', - node: nodeId, - provider: this.id, - identity: 'test_user' as IdentityId, + userCode: 'randomtestcode', }, - hPrev: null, - kid: nodeId, - privateKey, - seq: 1, - }); - - const claimJson = decodeClaim(claim); - - this.links = { - test_link: JSON.stringify(claimJson), }; - } - - public async *authenticate(): AsyncGenerator { - const code = 'randomtestcode'; - yield code; // Always gives back the abc123 token const tokenData = { accessToken: 'abc123' }; const identityId = await this.getIdentityId(tokenData);