@@ -18,7 +18,7 @@ function fromB64(data: string) {
1818
1919export class JobTokenManager {
2020 private secret : string ;
21- private usedJti = new Set < string > ( ) ;
21+ private usedJtiExp = new Map < string , number > ( ) ;
2222
2323 constructor ( secret ?: string ) {
2424 this . secret = secret ?? process . env . EDGEMESH_JOB_TOKEN_SECRET ?? "dev-secret" ;
@@ -51,8 +51,10 @@ export class JobTokenManager {
5151 return { ok : false as const , error : "token_payload_invalid" } ;
5252 }
5353
54+ this . pruneReplayCache ( ) ;
55+
5456 if ( Date . now ( ) > payload . exp ) return { ok : false as const , error : "token_expired" } ;
55- if ( this . usedJti . has ( payload . jti ) ) return { ok : false as const , error : "token_replay" } ;
57+ if ( this . usedJtiExp . has ( payload . jti ) ) return { ok : false as const , error : "token_replay" } ;
5658 if ( payload . jobId !== expected . jobId )
5759 return { ok : false as const , error : "token_job_mismatch" } ;
5860 if (
@@ -63,9 +65,20 @@ export class JobTokenManager {
6365 return { ok : false as const , error : "token_node_mismatch" } ;
6466 }
6567
66- this . usedJti . add ( payload . jti ) ;
68+ this . usedJtiExp . set ( payload . jti , payload . exp ) ;
6769 return { ok : true as const , payload } ;
6870 }
71+
72+ replayCacheSize ( ) {
73+ this . pruneReplayCache ( ) ;
74+ return this . usedJtiExp . size ;
75+ }
76+
77+ private pruneReplayCache ( now = Date . now ( ) ) {
78+ for ( const [ jti , exp ] of this . usedJtiExp . entries ( ) ) {
79+ if ( exp <= now ) this . usedJtiExp . delete ( jti ) ;
80+ }
81+ }
6982}
7083
7184export class NodeTrustManager {
0 commit comments