Skip to content

Commit 958bf21

Browse files
committed
feat: added duplex stream for connection authentication
chore: updated documentation fix: lint fix: import order fix: all tests now passing fix: auth handler hanging until timeout if errored
1 parent 615c5da commit 958bf21

13 files changed

+506
-195
lines changed

src/nodes/NodeConnectionManager.ts

Lines changed: 271 additions & 106 deletions
Large diffs are not rendered by default.

src/nodes/NodeManager.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,7 @@ import type {
1414
} from '../tasks/types.js';
1515
import type { SignedTokenEncoded } from '../tokens/types.js';
1616
import type { Host, Port } from '../network/types.js';
17-
import type {
18-
Claim,
19-
ClaimId,
20-
// ClaimIdEncoded,
21-
SignedClaim,
22-
} from '../claims/types.js';
17+
import type { Claim, ClaimId, SignedClaim } from '../claims/types.js';
2318
import type { ClaimLinkNode } from '../claims/payloads/index.js';
2419
import type NodeConnection from '../nodes/NodeConnection.js';
2520
import type {
@@ -48,8 +43,8 @@ import { decorators } from '@matrixai/contexts';
4843
import * as nodesUtils from './utils.js';
4944
import * as nodesEvents from './events.js';
5045
import * as nodesErrors from './errors.js';
51-
import * as agentErrors from './agent/errors.js';
5246
import NodeConnectionQueue from './NodeConnectionQueue.js';
47+
import config from '../config.js';
5348
import { assertClaimNetworkAuthority } from '../claims/payloads/claimNetworkAuthority.js';
5449
import { assertClaimNetworkAccess } from '../claims/payloads/claimNetworkAccess.js';
5550
import Token from '../tokens/Token.js';
@@ -58,7 +53,6 @@ import * as tasksErrors from '../tasks/errors.js';
5853
import * as claimsUtils from '../claims/utils.js';
5954
import * as claimsErrors from '../claims/errors.js';
6055
import * as utils from '../utils/utils.js';
61-
import config from '../config.js';
6256
import * as networkUtils from '../network/utils.js';
6357

6458
const abortEphemeralTaskReason = Symbol('abort ephemeral task reason');
@@ -1628,7 +1622,7 @@ class NodeManager {
16281622
}
16291623

16301624
if (!success) {
1631-
throw new agentErrors.ErrorNodesClaimNetworkVerificationFailed();
1625+
throw new nodesErrors.ErrorNodeClaimNetworkVerificationFailed();
16321626
}
16331627

16341628
return {

src/nodes/agent/errors.ts

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,4 @@ class ErrorAgentNodeIdMissing<T> extends ErrorAgent<T> {
88
exitCode = sysexits.UNAVAILABLE;
99
}
1010

11-
class ErrorNodesConnectionSignalRequestVerificationFailed<
12-
T,
13-
> extends ErrorAgent<T> {
14-
static description = 'Failed to verify request message signature';
15-
exitCode = sysexits.UNAVAILABLE;
16-
}
17-
18-
class ErrorNodesConnectionSignalRelayVerificationFailed<
19-
T,
20-
> extends ErrorAgent<T> {
21-
static description = 'Failed to verify relay message signature';
22-
exitCode = sysexits.UNAVAILABLE;
23-
}
24-
25-
class ErrorNodesClaimNetworkVerificationFailed<T> extends ErrorAgent<T> {
26-
static description = 'Failed to verify claim network message';
27-
exitCode = sysexits.UNAVAILABLE;
28-
}
29-
30-
export {
31-
ErrorAgentNodeIdMissing,
32-
ErrorNodesConnectionSignalRequestVerificationFailed,
33-
ErrorNodesConnectionSignalRelayVerificationFailed,
34-
ErrorNodesClaimNetworkVerificationFailed,
35-
};
11+
export { ErrorAgentNodeIdMissing };

src/nodes/agent/handlers/NodesAuthenticateConnection.ts

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -42,42 +42,13 @@ class NodesAuthenticateConnection extends DuplexHandler<
4242
throw new agentErrors.ErrorAgentNodeIdMissing();
4343
}
4444

45-
// Forward authentication message processing
46-
const {
47-
value: forwardMessageIn,
48-
}: {
49-
value: NodesAuthenticateConnectionMessage | SuccessMessage;
50-
} = await input.next();
51-
if (forwardMessageIn.type === 'success') throw new Error('exit');
52-
const forwardMessageOut = await nodeConnectionManager.handleAuthentication(
45+
// This async generator handles the back-and-forth communication to
46+
// authenticate a connection.
47+
yield* nodeConnectionManager.handleAuthentication(
5348
requestingNodeId,
54-
forwardMessageIn,
49+
input,
5550
ctx,
5651
);
57-
yield {
58-
type: 'success',
59-
success: true,
60-
};
61-
62-
// Sending authentication message
63-
yield forwardMessageOut;
64-
const {
65-
value: reverseMessageIn,
66-
}: {
67-
value: NodesAuthenticateConnectionMessage | SuccessMessage;
68-
} = await input.next();
69-
if (reverseMessageIn.type !== 'success') throw new Error('exit');
70-
71-
nodeConnectionManager.finalizeAuthentication(
72-
requestingNodeId,
73-
reverseMessageIn.success,
74-
);
75-
yield {
76-
type: 'success',
77-
success: true,
78-
}
79-
// success: true
80-
// fire authentication events before final ack
8152
};
8253
}
8354

src/nodes/agent/handlers/NodesConnectionSignalFinal.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as keysUtils from '../../../keys/utils/index.js';
1515
import * as ids from '../../../ids/index.js';
1616
import * as agentErrors from '../errors.js';
1717
import * as agentUtils from '../utils.js';
18+
import * as nodesErrors from '../../errors.js';
1819

1920
class NodesConnectionSignalFinal extends UnaryHandler<
2021
{
@@ -68,7 +69,7 @@ class NodesConnectionSignalFinal extends UnaryHandler<
6869
requestSignature,
6970
)
7071
) {
71-
throw new agentErrors.ErrorNodesConnectionSignalRequestVerificationFailed();
72+
throw new nodesErrors.ErrorNodeConnectionSignalRequestVerificationFailed();
7273
}
7374
// Checking relay message relaySignature.
7475
// relayData is just `<sourceNodeId><targetNodeId><Address><requestSignature>` concatenated.
@@ -83,7 +84,7 @@ class NodesConnectionSignalFinal extends UnaryHandler<
8384
if (
8485
!keysUtils.verifyWithPublicKey(relayPublicKey, relayData, relaySignature)
8586
) {
86-
throw new agentErrors.ErrorNodesConnectionSignalRelayVerificationFailed();
87+
throw new nodesErrors.ErrorNodeConnectionSignalRelayVerificationFailed();
8788
}
8889

8990
const host = input.address.host as Host;

src/nodes/agent/handlers/NodesConnectionSignalInitial.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { matchSync } from '../../../utils/index.js';
1414
import { never } from '../../../utils/index.js';
1515
import * as agentErrors from '../errors.js';
1616
import * as agentUtils from '../utils.js';
17+
import * as nodesErrors from '../../errors.js';
1718
import * as keysUtils from '../../../keys/utils/index.js';
1819
import * as ids from '../../../ids/index.js';
1920

@@ -55,7 +56,7 @@ class NodesConnectionSignalInitial extends UnaryHandler<
5556
const data = Buffer.concat([requestingNodeId, targetNodeId]);
5657
const sourcePublicKey = keysUtils.publicKeyFromNodeId(requestingNodeId);
5758
if (!keysUtils.verifyWithPublicKey(sourcePublicKey, data, signature)) {
58-
throw new agentErrors.ErrorNodesConnectionSignalRelayVerificationFailed();
59+
throw new nodesErrors.ErrorNodeConnectionSignalRelayVerificationFailed();
5960
}
6061
if (meta == null) never('Missing metadata from stream');
6162
const remoteHost = meta.remoteHost;

src/nodes/errors.ts

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,17 +48,21 @@ class ErrorNodeManagerAuthenticationFailed<T> extends ErrorNodeManager<T> {
4848
exitCode = sysexits.NOPERM;
4949
}
5050

51-
class ErrorNodeManagerAuthenticationFailedForward<T> extends ErrorNodes<T> {
51+
class ErrorNodeManagerAuthenticationFailedForward<
52+
T,
53+
> extends ErrorNodeManager<T> {
5254
static description = 'Failed to complete forward authentication';
5355
exitCode = sysexits.USAGE;
5456
}
5557

56-
class ErrorNodeManagerAuthenticationFailedReverse<T> extends ErrorNodes<T> {
58+
class ErrorNodeManagerAuthenticationFailedReverse<
59+
T,
60+
> extends ErrorNodeManager<T> {
5761
static description = 'Failed to complete reverse authentication';
5862
exitCode = sysexits.USAGE;
5963
}
6064

61-
class ErrorNodeManagerAuthenticatonTimedOut<T> extends ErrorNodes<T> {
65+
class ErrorNodeManagerAuthenticationTimedOut<T> extends ErrorNodeManager<T> {
6266
static description = 'Failed to complete authentication before timing out';
6367
exitCode = sysexits.USAGE;
6468
}
@@ -154,7 +158,7 @@ class ErrorNodeConnectionTransportGenericError<
154158
exitCode = sysexits.USAGE;
155159
}
156160

157-
class ErrorConnectionNodesEmpty<T> extends ErrorNodeConnection<T> {
161+
class ErrorNodeConnectionEmpty<T> extends ErrorNodeConnection<T> {
158162
static description = 'Nodes list to verify against was empty';
159163
exitCode = sysexits.USAGE;
160164
}
@@ -256,6 +260,30 @@ class ErrorNodeAuthenticationFailed<T> extends ErrorNodes<T> {
256260
exitCode = sysexits.NOPERM;
257261
}
258262

263+
class ErrorNodeAuthenticationInvalidProtocol<T> extends ErrorNodes<T> {
264+
static description = 'Invalid protocol used for node authentication';
265+
exitCode = sysexits.USAGE;
266+
}
267+
268+
class ErrorNodeConnectionSignalRequestVerificationFailed<
269+
T,
270+
> extends ErrorNodeConnection<T> {
271+
static description = 'Failed to verify request message signature';
272+
exitCode = sysexits.UNAVAILABLE;
273+
}
274+
275+
class ErrorNodeConnectionSignalRelayVerificationFailed<
276+
T,
277+
> extends ErrorNodeConnection<T> {
278+
static description = 'Failed to verify relay message signature';
279+
exitCode = sysexits.UNAVAILABLE;
280+
}
281+
282+
class ErrorNodeClaimNetworkVerificationFailed<T> extends ErrorNodes<T> {
283+
static description = 'Failed to verify claim network message';
284+
exitCode = sysexits.UNAVAILABLE;
285+
}
286+
259287
export {
260288
ErrorNodes,
261289
ErrorNodeManager,
@@ -269,7 +297,7 @@ export {
269297
ErrorNodeManagerAuthenticationFailed,
270298
ErrorNodeManagerAuthenticationFailedForward,
271299
ErrorNodeManagerAuthenticationFailedReverse,
272-
ErrorNodeManagerAuthenticatonTimedOut,
300+
ErrorNodeManagerAuthenticationTimedOut,
273301
ErrorNodeGraph,
274302
ErrorNodeGraphRunning,
275303
ErrorNodeGraphNotRunning,
@@ -288,7 +316,7 @@ export {
288316
ErrorNodeConnectionInternalError,
289317
ErrorNodeConnectionTransportUnknownError,
290318
ErrorNodeConnectionTransportGenericError,
291-
ErrorConnectionNodesEmpty,
319+
ErrorNodeConnectionEmpty,
292320
ErrorNodeConnectionManager,
293321
ErrorNodeConnectionManagerNotRunning,
294322
ErrorNodeConnectionManagerStopping,
@@ -304,4 +332,8 @@ export {
304332
ErrorNodePermissionDenied,
305333
ErrorNodeLookupNotFound,
306334
ErrorNodeAuthenticationFailed,
335+
ErrorNodeAuthenticationInvalidProtocol,
336+
ErrorNodeClaimNetworkVerificationFailed,
337+
ErrorNodeConnectionSignalRelayVerificationFailed,
338+
ErrorNodeConnectionSignalRequestVerificationFailed,
307339
};

src/nodes/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
import type { ContextTimed } from '@matrixai/contexts';
12
import type { DBTransaction, KeyPath, LevelPath } from '@matrixai/db';
2-
import type { X509Certificate } from '@peculiar/x509';
33
import type { QUICClientCrypto, QUICServerCrypto } from '@matrixai/quic';
4+
import type { X509Certificate } from '@peculiar/x509';
45
import type { Key, Certificate, CertificatePEM } from '../keys/types.js';
56
import type { Hostname, Port } from '../network/types.js';
67
import type {
@@ -18,7 +19,6 @@ import type {
1819
NodesAuthenticateConnectionMessageBasicPublic,
1920
NodesAuthenticateConnectionMessageNone,
2021
} from './agent/types.js';
21-
import type { ContextTimed } from '@matrixai/contexts';
2222
import dns from 'dns';
2323
import { utils as dbUtils } from '@matrixai/db';
2424
import { IdInternal } from '@matrixai/id';
@@ -566,7 +566,7 @@ async function verifyServerCertificateChain(
566566
};
567567
}
568568
if (nodeIds.length === 0) {
569-
throw new nodesErrors.ErrorConnectionNodesEmpty();
569+
throw new nodesErrors.ErrorNodeConnectionEmpty();
570570
}
571571
const certChain: Array<Readonly<X509Certificate>> = [];
572572
for (const certPEM of certPEMChain) {

src/utils/utils.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -547,6 +547,42 @@ async function importFS(fs?: FileSystem): Promise<FileSystem> {
547547
return fsImported;
548548
}
549549

550+
/**
551+
* Races a promise against a context. If the context is aborted, then the promise
552+
* rejects with the reason. Otherwise, the original value is returned upon
553+
* resolving.
554+
* @param prom The promise to resolve
555+
* @param ctx The ctx with which to race the promise against
556+
* @returns A promise which resolves to the prom value or errors if ctx aborts
557+
*/
558+
async function resultOrAbort<T>(
559+
prom: Promise<T>,
560+
ctx: ContextTimed,
561+
): Promise<T> {
562+
// Create an abort promise which rejects when ctx is aborted
563+
const { p: abortP, rejectP: rejectAbortP } = promise<never>();
564+
const abortHandler = () => {
565+
rejectAbortP(ctx.signal.reason);
566+
};
567+
if (ctx.signal.aborted) {
568+
abortHandler();
569+
} else {
570+
ctx.signal.addEventListener('abort', abortHandler, { once: true });
571+
}
572+
573+
// Race the original promise and abortP. If the original promise resolves
574+
// first, then it is returned. If the context aborts first, then the
575+
// promise is rejected.
576+
try {
577+
return await Promise.race([prom, abortP]);
578+
} catch (e) {
579+
Error.captureStackTrace(e);
580+
throw e;
581+
} finally {
582+
ctx.signal.removeEventListener('abort', abortHandler);
583+
}
584+
}
585+
550586
export {
551587
AsyncFunction,
552588
GeneratorFunction,
@@ -588,4 +624,5 @@ export {
588624
yieldMicro,
589625
setMaxListeners,
590626
importFS,
627+
resultOrAbort,
591628
};

tests/identities/IdentitiesManager.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ describe('IdentitiesManager', () => {
8686
await identitiesManager.getTokens('abc' as ProviderId);
8787
}).rejects.toThrow(identitiesErrors.ErrorIdentitiesManagerNotRunning);
8888
});
89-
test.only.prop([
89+
test.prop([
9090
identitiesTestUtils.identitiyIdArb,
9191
identitiesTestUtils.providerTokenArb,
9292
])('get, set and unset tokens', async (identityId, providerToken) => {

0 commit comments

Comments
 (0)