Skip to content

Commit 1cb0f20

Browse files
authored
Merge pull request #908 from MatrixAI/feature-polykey-auth-login
Added token verification and signing for login
2 parents 71a8473 + 75ccca7 commit 1cb0f20

File tree

8 files changed

+112
-1
lines changed

8 files changed

+112
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,4 @@ dist
130130
# editor
131131
.vscode/
132132
.idea/
133+
.prettierrc
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import type { HandlerTypes } from '@matrixai/rpc';
2+
import type AuthSignToken from '../handlers/AuthSignToken.js';
3+
import { UnaryCaller } from '@matrixai/rpc';
4+
5+
type CallerTypes = HandlerTypes<AuthSignToken>;
6+
7+
const authSignToken = new UnaryCaller<
8+
CallerTypes['input'],
9+
CallerTypes['output']
10+
>();
11+
12+
export default authSignToken;

src/client/callers/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import agentStop from './agentStop.js';
44
import agentUnlock from './agentUnlock.js';
55
import auditEventsGet from './auditEventsGet.js';
66
import auditMetricGet from './auditMetricGet.js';
7+
import authSignToken from './authSignToken.js';
78
import gestaltsActionsGetByIdentity from './gestaltsActionsGetByIdentity.js';
89
import gestaltsActionsGetByNode from './gestaltsActionsGetByNode.js';
910
import gestaltsActionsSetByIdentity from './gestaltsActionsSetByIdentity.js';
@@ -85,6 +86,7 @@ const clientManifest = {
8586
agentUnlock,
8687
auditEventsGet,
8788
auditMetricGet,
89+
authSignToken,
8890
gestaltsActionsGetByIdentity,
8991
gestaltsActionsGetByNode,
9092
gestaltsActionsSetByIdentity,
@@ -165,6 +167,7 @@ export {
165167
agentStop,
166168
agentUnlock,
167169
auditEventsGet,
170+
authSignToken,
168171
gestaltsActionsGetByIdentity,
169172
gestaltsActionsGetByNode,
170173
gestaltsActionsSetByIdentity,

src/client/errors.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ class ErrorClientVerificationFailed<T> extends ErrorClientService<T> {
5050
exitCode = sysexits.USAGE;
5151
}
5252

53+
class ErrorAuthentication<T> extends ErrorPolykey<T> {}
54+
55+
class ErrorAuthenticationInvalidToken<T> extends ErrorAuthentication<T> {
56+
static description = 'Token is invalid';
57+
exitCode = sysexits.PROTOCOL;
58+
}
59+
5360
export {
5461
ErrorClient,
5562
ErrorClientAuthMissing,
@@ -62,4 +69,6 @@ export {
6269
ErrorClientServiceNotRunning,
6370
ErrorClientServiceDestroyed,
6471
ErrorClientVerificationFailed,
72+
ErrorAuthentication,
73+
ErrorAuthenticationInvalidToken,
6574
};
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type {
2+
ClientRPCRequestParams,
3+
ClientRPCResponseResult,
4+
IdentityRequestData,
5+
IdentityResponseData,
6+
TokenIdentityRequest,
7+
TokenIdentityResponse,
8+
} from '../types.js';
9+
import type KeyRing from '../../keys/KeyRing.js';
10+
import type { PublicKey } from '../../keys/types.js';
11+
import { UnaryHandler } from '@matrixai/rpc';
12+
import Token from '../../tokens/Token.js';
13+
import * as clientErrors from '../errors.js';
14+
import * as nodesUtils from '../../nodes/utils.js';
15+
16+
class AuthSignToken extends UnaryHandler<
17+
{
18+
keyRing: KeyRing;
19+
},
20+
ClientRPCRequestParams<TokenIdentityRequest>,
21+
ClientRPCResponseResult<TokenIdentityResponse>
22+
> {
23+
public handle = async (
24+
input: ClientRPCRequestParams<TokenIdentityRequest>,
25+
): Promise<TokenIdentityResponse> => {
26+
const { keyRing }: { keyRing: KeyRing } = this.container;
27+
28+
// Get and verify incoming node
29+
const inputToken = { payload: input.payload, signatures: input.signatures };
30+
const incomingToken = Token.fromEncoded<IdentityRequestData>(inputToken);
31+
if (!('publicKey' in incomingToken.payload)) {
32+
throw new clientErrors.ErrorAuthenticationInvalidToken(
33+
'Input token does not contain public key',
34+
);
35+
}
36+
const incomingPublicKey = Buffer.from(
37+
incomingToken.payload.publicKey,
38+
'base64url',
39+
) as PublicKey;
40+
if (!incomingToken.verifyWithPublicKey(incomingPublicKey)) {
41+
throw new clientErrors.ErrorAuthenticationInvalidToken(
42+
'Incoming token does not match its signature',
43+
);
44+
}
45+
46+
// Create the outgoing token with the incoming token integrated into the
47+
// payload.
48+
const outgoingTokenPayload: IdentityResponseData = {
49+
requestToken: inputToken,
50+
nodeId: nodesUtils.encodeNodeId(keyRing.getNodeId()),
51+
};
52+
const outgoingToken =
53+
Token.fromPayload<IdentityResponseData>(outgoingTokenPayload);
54+
outgoingToken.signWithPrivateKey(keyRing.keyPair);
55+
return outgoingToken.toEncoded();
56+
};
57+
}
58+
59+
export default AuthSignToken;

src/client/handlers/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import AgentStop from './AgentStop.js';
2222
import AgentUnlock from './AgentUnlock.js';
2323
import AuditEventsGet from './AuditEventsGet.js';
2424
import AuditMetricGet from './AuditMetricGet.js';
25+
import AuthSignToken from './AuthSignToken.js';
2526
import GestaltsActionsGetByIdentity from './GestaltsActionsGetByIdentity.js';
2627
import GestaltsActionsGetByNode from './GestaltsActionsGetByNode.js';
2728
import GestaltsActionsSetByIdentity from './GestaltsActionsSetByIdentity.js';
@@ -122,6 +123,7 @@ const serverManifest = (container: {
122123
agentUnlock: new AgentUnlock(container),
123124
auditEventsGet: new AuditEventsGet(container),
124125
auditMetricGet: new AuditMetricGet(container),
126+
authSignToken: new AuthSignToken(container),
125127
gestaltsActionsGetByIdentity: new GestaltsActionsGetByIdentity(container),
126128
gestaltsActionsGetByNode: new GestaltsActionsGetByNode(container),
127129
gestaltsActionsSetByIdentity: new GestaltsActionsSetByIdentity(container),
@@ -208,6 +210,7 @@ export {
208210
AgentUnlock,
209211
AuditEventsGet,
210212
AuditMetricGet,
213+
AuthSignToken,
211214
GestaltsActionsGetByIdentity,
212215
GestaltsActionsGetByNode,
213216
GestaltsActionsSetByIdentity,

src/client/types.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import type {
2121
} from '../keys/types.js';
2222
import type { Notification } from '../notifications/types.js';
2323
import type { ProviderToken } from '../identities/types.js';
24+
import type { TokenPayload, SignedTokenEncoded } from '../tokens/types.js';
2425
import type { AuditMetricGetTypeOverride } from './callers/auditMetricGet.js';
2526
import type {
2627
NodeContact,
@@ -107,6 +108,22 @@ type TokenMessage = {
107108
token: ProviderToken;
108109
};
109110

111+
// Return URL must be present on the token, otherwise token contents is decided
112+
// by the client.
113+
type IdentityRequestData = TokenPayload & {
114+
returnURL: string;
115+
publicKey: string;
116+
};
117+
118+
type TokenIdentityRequest = SignedTokenEncoded;
119+
120+
type IdentityResponseData = TokenPayload & {
121+
requestToken: TokenIdentityRequest;
122+
nodeId: NodeIdEncoded;
123+
};
124+
125+
type TokenIdentityResponse = SignedTokenEncoded;
126+
110127
// Nodes messages
111128

112129
type NodeIdMessage = {
@@ -405,6 +422,10 @@ export type {
405422
ClaimIdMessage,
406423
ClaimNodeMessage,
407424
TokenMessage,
425+
IdentityRequestData,
426+
IdentityResponseData,
427+
TokenIdentityRequest,
428+
TokenIdentityResponse,
408429
NodeIdMessage,
409430
AddressMessage,
410431
NodeAddressMessage,

src/network/utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,10 @@ function fromError(error: any) {
477477
// serialising only the error type, message and its stack.
478478
const wrappedError = new errors.ErrorPolykeyUnexpected(
479479
`Unexpected error occurred: ${error.name}`,
480-
{ cause: error },
480+
{
481+
cause: error,
482+
data: { message: 'message' in error ? error.message : undefined },
483+
},
481484
);
482485
return wrappedError.toJSON();
483486
}

0 commit comments

Comments
 (0)