Skip to content

Commit 5159160

Browse files
committed
feat: added token verification and signing for login
1 parent 171d473 commit 5159160

File tree

5 files changed

+95
-0
lines changed

5 files changed

+95
-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: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import type {
1919
JWKEncrypted,
2020
PublicKeyJWK,
2121
} from '../keys/types.js';
22+
import type Token from '../tokens/Token.js';
2223
import type { Notification } from '../notifications/types.js';
2324
import type { ProviderToken } from '../identities/types.js';
2425
import type { AuditMetricGetTypeOverride } from './callers/auditMetricGet.js';
@@ -28,6 +29,7 @@ import type {
2829
NodeContactAddressData,
2930
} from '../nodes/types.js';
3031
import type { AuditEventsGetTypeOverride } from './callers/auditEventsGet.js';
32+
import { TokenPayload, SignedTokenJSON, SignedTokenEncoded, TokenPayloadEncoded } from '#src/types.js';
3133

3234
type ClientRPCRequestParams<T extends JSONObject = JSONObject> =
3335
JSONRPCResponseResult<
@@ -107,6 +109,22 @@ type TokenMessage = {
107109
token: ProviderToken;
108110
};
109111

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

112130
type NodeIdMessage = {
@@ -405,6 +423,10 @@ export type {
405423
ClaimIdMessage,
406424
ClaimNodeMessage,
407425
TokenMessage,
426+
IdentityRequestData,
427+
IdentityResponseData,
428+
TokenIdentityRequest,
429+
TokenIdentityResponse,
408430
NodeIdMessage,
409431
AddressMessage,
410432
NodeAddressMessage,

0 commit comments

Comments
 (0)