Skip to content

Commit e0da424

Browse files
feat: update connection for tokens, fix decryption in auth interceptor, fix tests
1 parent 27f7015 commit e0da424

File tree

8 files changed

+314
-160
lines changed

8 files changed

+314
-160
lines changed

Tokenization/backend/wrapper/src/client/Connection/Connection.ts

Lines changed: 126 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,90 +18,151 @@ import {
1818
ConnectionStatus,
1919
FetchOptions,
2020
FetchResponse,
21+
TokenPayload,
2122
} from "../../models/connection.model";
2223
import * as grpc from "@grpc/grpc-js";
24+
import { LogManager } from "@aliceo2/web-ui";
25+
26+
type ConnectionCerts = {
27+
caCert: NonSharedBuffer;
28+
clientCert: NonSharedBuffer;
29+
clientKey: NonSharedBuffer;
30+
};
2331

2432
/**
2533
* @description This class represents a connection to a target client and manages sending messages to it.
2634
*/
2735
export class Connection {
28-
private token: string;
36+
private jweToken: string;
2937
private status: ConnectionStatus;
3038
private peerClient?: any; // a client grpc connection instance
3139

40+
// security management variables
41+
private clientSerialNumber?: string; // The certificate SN used to uniquely identify the peer.
42+
private lastActiveTimestamp: number; // Timestamp of the last successful request (for garbage collection).
43+
private authFailures: number; // Counter for consecutive authentication failures (for anti-DDoS/throttling).
44+
private cachedTokenPayload?: TokenPayload; // Cache of the successfully verified token payload.
45+
3246
public targetAddress: string;
3347
public direction: ConnectionDirection;
3448

49+
// utils
50+
private logger;
51+
3552
/**
3653
* @description Creates a new Connection instance with the given token, target address, and connection direction.
3754
*
38-
* @param token - The authentication token for the connection.
55+
* @param jweToken - The encrypted JWE token for the connection.
3956
* @param targetAddress - The unique address of the target client.
4057
* @param direction - The direction of the connection (e.g., sending or receiving).
41-
* @param peerCtor - The constructor for the gRPC client to be used for communication.
42-
* @param caCertPath - Path to the CA certificate file.
43-
* @param clientCertPath - Path to the client certificate file.
44-
* @param clientKeyPath - Path to the client key file.
58+
* @param clientSN - Optional serial number of the peer's certificate (used for lookups).
4559
*/
4660
constructor(
47-
token: string,
61+
jweToken: string,
4862
targetAddress: string,
4963
direction: ConnectionDirection,
50-
peerCtor: any,
51-
private readonly connectionCerts: {
52-
caCert: NonSharedBuffer;
53-
clientCert: NonSharedBuffer;
54-
clientKey: NonSharedBuffer;
55-
}
64+
clientSN?: string
5665
) {
57-
this.token = token;
66+
this.jweToken = jweToken;
5867
this.targetAddress = targetAddress;
5968
this.direction = direction;
6069

70+
// Initialize state fields
71+
this.clientSerialNumber = clientSN;
72+
this.lastActiveTimestamp = Date.now();
73+
this.authFailures = 0;
74+
this.status = ConnectionStatus.CONNECTED;
75+
76+
this.logger = LogManager.getLogger(`Connection ${targetAddress}`);
77+
}
78+
79+
/**
80+
* @description Creates the mTLS gRPC client and attaches it to the connection.
81+
* This method is REQUIRED ONLY for outbound (SENDING) connections.
82+
* * @param peerCtor - The constructor for the gRPC client to be used for communication.
83+
* @param connectionCerts - Required sending client certificates for mTLS.
84+
*/
85+
public createSslTunnel(
86+
peerCtor: any,
87+
connectionCerts: ConnectionCerts
88+
): void {
89+
if (this.direction !== ConnectionDirection.SENDING) {
90+
this.logger.warnMessage(
91+
"Attempted to create SSL tunnel on a RECEIVING connection. This is usually unnecessary."
92+
);
93+
}
94+
6195
if (
6296
!connectionCerts.caCert ||
6397
!connectionCerts.clientCert ||
6498
!connectionCerts.clientKey
6599
) {
66100
throw new Error(
67-
"Connection certificates are required to create a Connection."
101+
"Connection certificates are required to create an mTLS tunnel."
68102
);
69103
}
70104

71105
// create grpc credentials
72106
const sslCreds = grpc.credentials.createSsl(
73-
this.connectionCerts.caCert,
74-
this.connectionCerts.clientKey,
75-
this.connectionCerts.clientCert
107+
connectionCerts.caCert,
108+
connectionCerts.clientKey,
109+
connectionCerts.clientCert
76110
);
77111

78-
this.peerClient = new peerCtor(targetAddress, sslCreds);
79-
80-
this.status = ConnectionStatus.CONNECTED;
112+
this.peerClient = new peerCtor(this.targetAddress, sslCreds);
113+
this.updateStatus(ConnectionStatus.CONNECTED);
81114
}
82115

83116
/**
84117
* @description Replace newly generated token
85-
* @param token New token to be replaced
118+
* @param jweToken New token to be replaced
86119
*/
87-
public handleNewToken(token: string): void {
88-
this.token = token;
120+
public handleNewToken(jweToken: string): void {
121+
this.jweToken = jweToken;
89122
}
90123

91124
/**
92125
* @description Revoke current token and set status of unauthorized connection
93126
*/
94127
public handleRevokeToken(): void {
95-
this.token = "";
128+
this.jweToken = "";
96129
this.status = ConnectionStatus.UNAUTHORIZED;
97130
}
98131

132+
/**
133+
* @description Handles a successful authentication event. Updates the active timestamp,
134+
* resets the failure counter, and caches the new token payload.
135+
* This is crucial for high-performance applications to avoid re-validating the same token.
136+
* @param payload The decoded and verified token payload.
137+
*/
138+
public handleSuccessfulAuth(payload: TokenPayload): void {
139+
this.lastActiveTimestamp = Date.now();
140+
this.authFailures = 0;
141+
this.cachedTokenPayload = payload;
142+
this.updateStatus(ConnectionStatus.CONNECTED);
143+
}
144+
145+
/**
146+
* @description Handles an authentication failure. Increments the failure counter.
147+
* If the failure count exceeds a local threshold, the connection is locally marked as BLOCKED.
148+
* @returns The new count of consecutive failures.
149+
*/
150+
public handleFailedAuth(): number {
151+
this.authFailures += 1;
152+
153+
// Local throttling mechanism
154+
if (this.authFailures >= 5) {
155+
this.updateStatus(ConnectionStatus.BLOCKED);
156+
}
157+
return this.authFailures;
158+
}
159+
99160
/**
100161
* @description Returns token for this Connection object
101162
* @returns Connection token
102163
*/
103164
public getToken(): string {
104-
return this.token;
165+
return this.jweToken;
105166
}
106167

107168
/**
@@ -128,6 +189,39 @@ export class Connection {
128189
return this.targetAddress;
129190
}
130191

192+
/**
193+
* @description Returns the client's Serial Number (SN).
194+
* @returns The client's serial number or undefined.
195+
*/
196+
public getSerialNumber(): string | undefined {
197+
return this.clientSerialNumber;
198+
}
199+
200+
/**
201+
* @description Sets the client's Serial Number. Primarily used for RECEIVING connections
202+
* where the SN is extracted during the first mTLS handshake in the interceptor.
203+
* @param serialNumber The serial number string.
204+
*/
205+
public setSerialNumber(serialNumber: string): void {
206+
this.clientSerialNumber = serialNumber;
207+
}
208+
209+
/**
210+
* @description Returns the timestamp of the last successful interaction.
211+
* @returns UNIX timestamp in milliseconds.
212+
*/
213+
public getLastActiveTimestamp(): number {
214+
return this.lastActiveTimestamp;
215+
}
216+
217+
/**
218+
* @description Returns the cached token payload.
219+
* @returns The cached payload or undefined.
220+
*/
221+
public getCachedTokenPayload(): TokenPayload | undefined {
222+
return this.cachedTokenPayload;
223+
}
224+
131225
/**
132226
* @description Attaches gRPC client to that connection
133227
*/
@@ -151,6 +245,11 @@ export class Connection {
151245
const path = options.path || "/";
152246
const headers: ConnectionHeaders = { ...(options.headers || {}) };
153247

248+
// set mandatory grpc metadata
249+
const metadata = new grpc.Metadata();
250+
metadata.set("jweToken", this.jweToken);
251+
252+
// build body buffer
154253
let bodyBuf: Buffer = Buffer.alloc(0);
155254
const b = options.body;
156255
if (b != null) {
@@ -167,7 +266,7 @@ export class Connection {
167266

168267
// return promise with response
169268
return new Promise<FetchResponse>((resolve, reject) => {
170-
this.peerClient.Fetch(req, (err: any, resp: any) => {
269+
this.peerClient.Fetch(req, metadata, (err: any, resp: any) => {
171270
if (err) return reject(err);
172271

173272
const resBody = resp?.body ? Buffer.from(resp.body) : Buffer.alloc(0);

Tokenization/backend/wrapper/src/client/ConnectionManager/ConnectionManager.ts

Lines changed: 37 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -114,23 +114,6 @@ export class ConnectionManager {
114114
centralClient,
115115
this.centralDispatcher
116116
);
117-
118-
this.sendingConnections.set(
119-
"a",
120-
new Connection("1", "a", ConnectionDirection.SENDING, this.peerCtor, {
121-
caCert: this.caCert,
122-
clientCert: this.clientCert,
123-
clientKey: this.clientKey,
124-
})
125-
);
126-
this.sendingConnections.set(
127-
"b",
128-
new Connection("2", "b", ConnectionDirection.SENDING, this.peerCtor, {
129-
caCert: this.caCert,
130-
clientCert: this.clientCert,
131-
clientKey: this.clientKey,
132-
})
133-
);
134117
}
135118

136119
/**
@@ -166,12 +149,12 @@ export class ConnectionManager {
166149
* Creates new connection
167150
* @param address Target (external) address of the connection
168151
* @param direction Direction of connection
169-
* @param token Optional token for connection
152+
* @param jweToken Optional encrypted JWE token for connection
170153
*/
171154
public async createNewConnection(
172155
address: string,
173156
direction: ConnectionDirection,
174-
token?: string
157+
jweToken?: string
175158
) {
176159
let conn: Connection | undefined;
177160

@@ -183,14 +166,15 @@ export class ConnectionManager {
183166

184167
// Return existing connection if found
185168
if (conn) {
186-
if (token) {
187-
conn.handleNewToken(token);
169+
if (jweToken) {
170+
conn.handleNewToken(jweToken);
188171
}
189172
return conn;
190173
}
191174

192175
// Create new connection
193-
conn = new Connection(token || "", address, direction, this.peerCtor, {
176+
conn = new Connection(jweToken || "", address, direction);
177+
conn.createSslTunnel(this.peerCtor, {
194178
caCert: this.caCert,
195179
clientCert: this.clientCert,
196180
clientKey: this.clientKey,
@@ -229,6 +213,27 @@ export class ConnectionManager {
229213
}
230214
}
231215

216+
/**
217+
* @description Searches through all receiving and sending connections to find a connection by its client Serial Number (SN).
218+
* @param serialNumber The unique serial number of the peer's certificate.
219+
* @returns The matching Connection object or undefined.
220+
*/
221+
getConnectionBySerialNumber(serialNumber: string): Connection | undefined {
222+
// Check receiving connections first
223+
for (const conn of this.receivingConnections.values()) {
224+
if (conn.getSerialNumber() === serialNumber) {
225+
return conn;
226+
}
227+
}
228+
// Check sending connections
229+
for (const conn of this.sendingConnections.values()) {
230+
if (conn.getSerialNumber() === serialNumber) {
231+
return conn;
232+
}
233+
}
234+
return undefined;
235+
}
236+
232237
/**
233238
* Returns object with all connections
234239
* @returns Object of all connections
@@ -246,7 +251,6 @@ export class ConnectionManager {
246251
/** Starts a listener server for p2p connections */
247252
public async listenForPeers(
248253
port: number,
249-
listenerPublicKey: NonSharedBuffer,
250254
listenerCert: NonSharedBuffer,
251255
listenerPrivateKey: NonSharedBuffer,
252256
baseAPIPath?: string
@@ -265,39 +269,25 @@ export class ConnectionManager {
265269
callback: grpc.sendUnaryData<any>
266270
) => {
267271
// run auth interceptor
268-
gRPCAuthInterceptor(
272+
const { isAuthenticated, conn } = await gRPCAuthInterceptor(
269273
call,
270274
callback,
271275
this.receivingConnections,
272276
listenerPrivateKey,
273-
listenerPublicKey
277+
this.peerCtor
274278
);
275279

280+
if (!isAuthenticated || !conn) {
281+
// Authentication failed - response already sent in interceptor
282+
return;
283+
}
284+
276285
try {
277286
const clientAddress = call.getPeer();
278287
this.logger.infoMessage(`Incoming request from ${clientAddress}`);
279288

280-
let conn: Connection | undefined =
281-
this.receivingConnections.get(clientAddress);
282-
283-
if (!conn) {
284-
conn = new Connection(
285-
"",
286-
clientAddress,
287-
ConnectionDirection.RECEIVING,
288-
this.peerCtor,
289-
{
290-
caCert: this.caCert,
291-
clientCert: this.clientCert,
292-
clientKey: this.clientKey,
293-
}
294-
);
295-
conn.updateStatus(ConnectionStatus.CONNECTED);
296-
this.receivingConnections.set(clientAddress, conn);
297-
this.logger.infoMessage(
298-
`New incoming connection registered for: ${clientAddress}`
299-
);
300-
}
289+
conn.updateStatus(ConnectionStatus.CONNECTED);
290+
this.receivingConnections.set(clientAddress, conn);
301291

302292
// create request to forward to local API endpoint
303293
const method = String(call.request?.method || "POST").toUpperCase();
@@ -345,7 +335,7 @@ export class ConnectionManager {
345335
this.caCert,
346336
[
347337
{
348-
private_key: listenerPublicKey,
338+
private_key: listenerPrivateKey,
349339
cert_chain: listenerCert,
350340
},
351341
],
@@ -360,6 +350,4 @@ export class ConnectionManager {
360350

361351
this.logger.infoMessage(`Peer server listening on localhost:${port}`);
362352
}
363-
364-
private createPeerAuthInterceptor() {}
365353
}

0 commit comments

Comments
 (0)