Skip to content

Commit 9a3d8ee

Browse files
committed
feat: added token verification and signing for login
fix: lint
1 parent 171d473 commit 9a3d8ee

File tree

5 files changed

+94
-0
lines changed

5 files changed

+94
-0
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/AgentLockAll.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/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 = 'Incoming token does not match its signature';
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: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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+
const incomingPublicKey = Buffer.from(
32+
incomingToken.payload.publicKey,
33+
) as PublicKey;
34+
if (!incomingToken.verifyWithPublicKey(incomingPublicKey)) {
35+
throw new clientErrors.ErrorAuthenticationInvalidToken();
36+
}
37+
38+
// Create the outgoing token with the incoming token integrated into the
39+
// payload.
40+
const outgoingTokenPayload: IdentityResponseData = {
41+
requestToken: inputToken,
42+
nodeId: nodesUtils.encodeNodeId(keyRing.getNodeId()),
43+
};
44+
const outgoingToken =
45+
Token.fromPayload<IdentityResponseData>(outgoingTokenPayload);
46+
outgoingToken.signWithPrivateKey(keyRing.keyPair);
47+
return outgoingToken.toEncoded();
48+
};
49+
}
50+
51+
export default AuthSignToken;

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,

0 commit comments

Comments
 (0)