@@ -18,90 +18,151 @@ import {
1818 ConnectionStatus ,
1919 FetchOptions ,
2020 FetchResponse ,
21+ TokenPayload ,
2122} from "../../models/connection.model" ;
2223import * 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 */
2735export 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 ) ;
0 commit comments