Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -130,3 +130,4 @@ dist
# editor
.vscode/
.idea/
.prettierrc
12 changes: 12 additions & 0 deletions src/client/callers/authSignToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { HandlerTypes } from '@matrixai/rpc';
import type AuthSignToken from '../handlers/AuthSignToken.js';
import { UnaryCaller } from '@matrixai/rpc';

type CallerTypes = HandlerTypes<AuthSignToken>;

const authSignToken = new UnaryCaller<
CallerTypes['input'],
CallerTypes['output']
>();

export default authSignToken;
3 changes: 3 additions & 0 deletions src/client/callers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -85,6 +86,7 @@ const clientManifest = {
agentUnlock,
auditEventsGet,
auditMetricGet,
authSignToken,
gestaltsActionsGetByIdentity,
gestaltsActionsGetByNode,
gestaltsActionsSetByIdentity,
Expand Down Expand Up @@ -165,6 +167,7 @@ export {
agentStop,
agentUnlock,
auditEventsGet,
authSignToken,
gestaltsActionsGetByIdentity,
gestaltsActionsGetByNode,
gestaltsActionsSetByIdentity,
Expand Down
9 changes: 9 additions & 0 deletions src/client/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,13 @@ class ErrorClientVerificationFailed<T> extends ErrorClientService<T> {
exitCode = sysexits.USAGE;
}

class ErrorAuthentication<T> extends ErrorPolykey<T> {}

class ErrorAuthenticationInvalidToken<T> extends ErrorAuthentication<T> {
static description = 'Token is invalid';
exitCode = sysexits.PROTOCOL;
}

export {
ErrorClient,
ErrorClientAuthMissing,
Expand All @@ -62,4 +69,6 @@ export {
ErrorClientServiceNotRunning,
ErrorClientServiceDestroyed,
ErrorClientVerificationFailed,
ErrorAuthentication,
ErrorAuthenticationInvalidToken,
};
59 changes: 59 additions & 0 deletions src/client/handlers/AuthSignToken.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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<TokenIdentityRequest>,
ClientRPCResponseResult<TokenIdentityResponse>
> {
public handle = async (
input: ClientRPCRequestParams<TokenIdentityRequest>,
): Promise<TokenIdentityResponse> => {
const { keyRing }: { keyRing: KeyRing } = this.container;

// Get and verify incoming node
const inputToken = { payload: input.payload, signatures: input.signatures };
const incomingToken = Token.fromEncoded<IdentityRequestData>(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(
'Incoming token does not match its signature',
);
}

// 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<IdentityResponseData>(outgoingTokenPayload);
outgoingToken.signWithPrivateKey(keyRing.keyPair);
return outgoingToken.toEncoded();
};
}

export default AuthSignToken;
3 changes: 3 additions & 0 deletions src/client/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -208,6 +210,7 @@ export {
AgentUnlock,
AuditEventsGet,
AuditMetricGet,
AuthSignToken,
GestaltsActionsGetByIdentity,
GestaltsActionsGetByNode,
GestaltsActionsSetByIdentity,
Expand Down
21 changes: 21 additions & 0 deletions src/client/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -405,6 +422,10 @@ export type {
ClaimIdMessage,
ClaimNodeMessage,
TokenMessage,
IdentityRequestData,
IdentityResponseData,
TokenIdentityRequest,
TokenIdentityResponse,
NodeIdMessage,
AddressMessage,
NodeAddressMessage,
Expand Down
5 changes: 4 additions & 1 deletion src/network/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down