From 9a3d8eef4e79d3a6ef92ffaebe14e30f284f4658 Mon Sep 17 00:00:00 2001 From: Aryan Jassal Date: Tue, 17 Jun 2025 14:02:22 +1000 Subject: [PATCH 1/3] feat: added token verification and signing for login fix: lint --- .gitignore | 1 + src/client/callers/authSignToken.ts | 12 +++++++ src/client/errors.ts | 9 +++++ src/client/handlers/AuthSignToken.ts | 51 ++++++++++++++++++++++++++++ src/client/types.ts | 21 ++++++++++++ 5 files changed, 94 insertions(+) create mode 100644 src/client/callers/authSignToken.ts create mode 100644 src/client/handlers/AuthSignToken.ts diff --git a/.gitignore b/.gitignore index 78a1b31bb3..c827a4749d 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,4 @@ dist # editor .vscode/ .idea/ +.prettierrc diff --git a/src/client/callers/authSignToken.ts b/src/client/callers/authSignToken.ts new file mode 100644 index 0000000000..8958a6a5b4 --- /dev/null +++ b/src/client/callers/authSignToken.ts @@ -0,0 +1,12 @@ +import type { HandlerTypes } from '@matrixai/rpc'; +import type AuthSignToken from '../handlers/AgentLockAll.js'; +import { UnaryCaller } from '@matrixai/rpc'; + +type CallerTypes = HandlerTypes; + +const authSignToken = new UnaryCaller< + CallerTypes['input'], + CallerTypes['output'] +>(); + +export default authSignToken; diff --git a/src/client/errors.ts b/src/client/errors.ts index 24cee22b75..7ea421efea 100644 --- a/src/client/errors.ts +++ b/src/client/errors.ts @@ -50,6 +50,13 @@ class ErrorClientVerificationFailed extends ErrorClientService { exitCode = sysexits.USAGE; } +class ErrorAuthentication extends ErrorPolykey {} + +class ErrorAuthenticationInvalidToken extends ErrorAuthentication { + static description = 'Incoming token does not match its signature'; + exitCode = sysexits.PROTOCOL; +} + export { ErrorClient, ErrorClientAuthMissing, @@ -62,4 +69,6 @@ export { ErrorClientServiceNotRunning, ErrorClientServiceDestroyed, ErrorClientVerificationFailed, + ErrorAuthentication, + ErrorAuthenticationInvalidToken, }; diff --git a/src/client/handlers/AuthSignToken.ts b/src/client/handlers/AuthSignToken.ts new file mode 100644 index 0000000000..8c734e387c --- /dev/null +++ b/src/client/handlers/AuthSignToken.ts @@ -0,0 +1,51 @@ +import type { + ClientRPCRequestParams, + ClientRPCResponseResult, + IdentityRequestData, + IdentityResponseData, + TokenIdentityRequest, + TokenIdentityResponse, +} from '../types.js'; +import type KeyRing from '../../keys/KeyRing.js'; +import type { PublicKey } from '../../keys/types.js'; +import { UnaryHandler } from '@matrixai/rpc'; +import Token from '../../tokens/Token.js'; +import * as clientErrors from '../errors.js'; +import * as nodesUtils from '../../nodes/utils.js'; + +class AuthSignToken extends UnaryHandler< + { + keyRing: KeyRing; + }, + ClientRPCRequestParams, + ClientRPCResponseResult +> { + public handle = async ( + input: ClientRPCRequestParams, + ): Promise => { + const { keyRing }: { keyRing: KeyRing } = this.container; + + // Get and verify incoming node + const inputToken = { payload: input.payload, signatures: input.signatures }; + const incomingToken = Token.fromEncoded(inputToken); + const incomingPublicKey = Buffer.from( + incomingToken.payload.publicKey, + ) as PublicKey; + if (!incomingToken.verifyWithPublicKey(incomingPublicKey)) { + throw new clientErrors.ErrorAuthenticationInvalidToken(); + } + + // Create the outgoing token with the incoming token integrated into the + // payload. + const outgoingTokenPayload: IdentityResponseData = { + requestToken: inputToken, + nodeId: nodesUtils.encodeNodeId(keyRing.getNodeId()), + }; + const outgoingToken = + Token.fromPayload(outgoingTokenPayload); + outgoingToken.signWithPrivateKey(keyRing.keyPair); + return outgoingToken.toEncoded(); + }; +} + +export default AuthSignToken; diff --git a/src/client/types.ts b/src/client/types.ts index 8e08254fe8..8223de19ff 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -21,6 +21,7 @@ import type { } from '../keys/types.js'; import type { Notification } from '../notifications/types.js'; import type { ProviderToken } from '../identities/types.js'; +import type { TokenPayload, SignedTokenEncoded } from '../tokens/types.js'; import type { AuditMetricGetTypeOverride } from './callers/auditMetricGet.js'; import type { NodeContact, @@ -107,6 +108,22 @@ type TokenMessage = { token: ProviderToken; }; +// Return URL must be present on the token, otherwise token contents is decided +// by the client. +type IdentityRequestData = TokenPayload & { + returnUrl: string; + publicKey: string; +}; + +type TokenIdentityRequest = SignedTokenEncoded; + +type IdentityResponseData = TokenPayload & { + requestToken: TokenIdentityRequest; + nodeId: NodeIdEncoded; +}; + +type TokenIdentityResponse = SignedTokenEncoded; + // Nodes messages type NodeIdMessage = { @@ -405,6 +422,10 @@ export type { ClaimIdMessage, ClaimNodeMessage, TokenMessage, + IdentityRequestData, + IdentityResponseData, + TokenIdentityRequest, + TokenIdentityResponse, NodeIdMessage, AddressMessage, NodeAddressMessage, From df4ed99e97e0e86616443d7a54bf49874ae0fcc5 Mon Sep 17 00:00:00 2001 From: Aryan Jassal Date: Tue, 17 Jun 2025 14:19:41 +1000 Subject: [PATCH 2/3] chore: added caller and handler to manifest --- src/client/callers/index.ts | 3 +++ src/client/handlers/index.ts | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/client/callers/index.ts b/src/client/callers/index.ts index 560b3a4eb4..d3aa78c8e9 100644 --- a/src/client/callers/index.ts +++ b/src/client/callers/index.ts @@ -4,6 +4,7 @@ import agentStop from './agentStop.js'; import agentUnlock from './agentUnlock.js'; import auditEventsGet from './auditEventsGet.js'; import auditMetricGet from './auditMetricGet.js'; +import authSignToken from './authSignToken.js'; import gestaltsActionsGetByIdentity from './gestaltsActionsGetByIdentity.js'; import gestaltsActionsGetByNode from './gestaltsActionsGetByNode.js'; import gestaltsActionsSetByIdentity from './gestaltsActionsSetByIdentity.js'; @@ -85,6 +86,7 @@ const clientManifest = { agentUnlock, auditEventsGet, auditMetricGet, + authSignToken, gestaltsActionsGetByIdentity, gestaltsActionsGetByNode, gestaltsActionsSetByIdentity, @@ -165,6 +167,7 @@ export { agentStop, agentUnlock, auditEventsGet, + authSignToken, gestaltsActionsGetByIdentity, gestaltsActionsGetByNode, gestaltsActionsSetByIdentity, diff --git a/src/client/handlers/index.ts b/src/client/handlers/index.ts index 037eb03184..d846ed0932 100644 --- a/src/client/handlers/index.ts +++ b/src/client/handlers/index.ts @@ -22,6 +22,7 @@ import AgentStop from './AgentStop.js'; import AgentUnlock from './AgentUnlock.js'; import AuditEventsGet from './AuditEventsGet.js'; import AuditMetricGet from './AuditMetricGet.js'; +import AuthSignToken from './AuthSignToken.js'; import GestaltsActionsGetByIdentity from './GestaltsActionsGetByIdentity.js'; import GestaltsActionsGetByNode from './GestaltsActionsGetByNode.js'; import GestaltsActionsSetByIdentity from './GestaltsActionsSetByIdentity.js'; @@ -122,6 +123,7 @@ const serverManifest = (container: { agentUnlock: new AgentUnlock(container), auditEventsGet: new AuditEventsGet(container), auditMetricGet: new AuditMetricGet(container), + authSignToken: new AuthSignToken(container), gestaltsActionsGetByIdentity: new GestaltsActionsGetByIdentity(container), gestaltsActionsGetByNode: new GestaltsActionsGetByNode(container), gestaltsActionsSetByIdentity: new GestaltsActionsSetByIdentity(container), @@ -208,6 +210,7 @@ export { AgentUnlock, AuditEventsGet, AuditMetricGet, + AuthSignToken, GestaltsActionsGetByIdentity, GestaltsActionsGetByNode, GestaltsActionsSetByIdentity, From 75ccca7168e5f36aa07aaba65fc329bb43c45b68 Mon Sep 17 00:00:00 2001 From: Aryan Jassal Date: Wed, 18 Jun 2025 13:09:14 +1000 Subject: [PATCH 3/3] chore: cleaned up error reporting chore: cleaned up code --- src/client/callers/authSignToken.ts | 2 +- src/client/errors.ts | 2 +- src/client/handlers/AuthSignToken.ts | 10 +++++++++- src/client/types.ts | 2 +- src/network/utils.ts | 5 ++++- 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/client/callers/authSignToken.ts b/src/client/callers/authSignToken.ts index 8958a6a5b4..34861e6459 100644 --- a/src/client/callers/authSignToken.ts +++ b/src/client/callers/authSignToken.ts @@ -1,5 +1,5 @@ import type { HandlerTypes } from '@matrixai/rpc'; -import type AuthSignToken from '../handlers/AgentLockAll.js'; +import type AuthSignToken from '../handlers/AuthSignToken.js'; import { UnaryCaller } from '@matrixai/rpc'; type CallerTypes = HandlerTypes; diff --git a/src/client/errors.ts b/src/client/errors.ts index 7ea421efea..476b7a2e64 100644 --- a/src/client/errors.ts +++ b/src/client/errors.ts @@ -53,7 +53,7 @@ class ErrorClientVerificationFailed extends ErrorClientService { class ErrorAuthentication extends ErrorPolykey {} class ErrorAuthenticationInvalidToken extends ErrorAuthentication { - static description = 'Incoming token does not match its signature'; + static description = 'Token is invalid'; exitCode = sysexits.PROTOCOL; } diff --git a/src/client/handlers/AuthSignToken.ts b/src/client/handlers/AuthSignToken.ts index 8c734e387c..04910b7229 100644 --- a/src/client/handlers/AuthSignToken.ts +++ b/src/client/handlers/AuthSignToken.ts @@ -28,11 +28,19 @@ class AuthSignToken extends UnaryHandler< // Get and verify incoming node const inputToken = { payload: input.payload, signatures: input.signatures }; const incomingToken = Token.fromEncoded(inputToken); + if (!('publicKey' in incomingToken.payload)) { + throw new clientErrors.ErrorAuthenticationInvalidToken( + 'Input token does not contain public key', + ); + } const incomingPublicKey = Buffer.from( incomingToken.payload.publicKey, + 'base64url', ) as PublicKey; if (!incomingToken.verifyWithPublicKey(incomingPublicKey)) { - throw new clientErrors.ErrorAuthenticationInvalidToken(); + throw new clientErrors.ErrorAuthenticationInvalidToken( + 'Incoming token does not match its signature', + ); } // Create the outgoing token with the incoming token integrated into the diff --git a/src/client/types.ts b/src/client/types.ts index 8223de19ff..e9900e89d8 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -111,7 +111,7 @@ type TokenMessage = { // Return URL must be present on the token, otherwise token contents is decided // by the client. type IdentityRequestData = TokenPayload & { - returnUrl: string; + returnURL: string; publicKey: string; }; diff --git a/src/network/utils.ts b/src/network/utils.ts index 253d2bcb5d..c6e252a977 100644 --- a/src/network/utils.ts +++ b/src/network/utils.ts @@ -477,7 +477,10 @@ function fromError(error: any) { // serialising only the error type, message and its stack. const wrappedError = new errors.ErrorPolykeyUnexpected( `Unexpected error occurred: ${error.name}`, - { cause: error }, + { + cause: error, + data: { message: 'message' in error ? error.message : undefined }, + }, ); return wrappedError.toJSON(); }