@vizamodo/aws-sts-core 0.3.2 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/sts/issue.js +92 -59
  2. package/package.json +1 -1
package/dist/sts/issue.js CHANGED
@@ -25,6 +25,7 @@ let cachedPrivateKeyBase64 = null;
25
25
  // ---- cached certificate serial (DER parse is expensive, cert rarely changes) ----
26
26
  let cachedCertSerialDec = null;
27
27
  let cachedCertSerialSource = null;
28
+ const stsCredentialCache = new Map();
28
29
  // ---- shared encoder ----
29
30
  const textEncoder = new TextEncoder();
30
31
  // Precomputed hex table for fast byte→hex conversion (no per-byte toString alloc)
@@ -78,22 +79,7 @@ export async function issueAwsCredentials(input) {
78
79
  // Normalize certificate once so every subsystem (cache, DER parse, headers)
79
80
  // uses the exact same canonical string
80
81
  const normalizedCert = normalizeCert(certBase64);
81
- const { signingKey } = await getSigningMaterial({
82
- certBase64: normalizedCert,
83
- privateKeyPkcs8Base64,
84
- });
85
- // 2. Setup constants
86
- const host = `${SERVICE}.${region}.amazonaws.com`;
87
- const path = PATH;
88
- const now = new Date();
89
- const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
90
- const dateStamp = amzDate.slice(0, 8);
91
- const service = SERVICE;
92
- // 3. Chuẩn bị Body & Cert
93
- const body = JSON.stringify({ trustAnchorArn, profileArn, roleArn, durationSeconds: sessionTtl });
94
- const payloadHash = await getPayloadHash(body);
95
- // --- REFACTOR: Làm sạch chứng chỉ chặt chẽ ---
96
- // const normalizedCert = normalizeCert(certBase64);
82
+ let cacheKey = "";
97
83
  // --- REFACTOR: Parser ASN.1 an toàn để lấy Serial Number (DECIMAL) ---
98
84
  // --- MINIMAL DER WALK: extract serial number ---
99
85
  let certSerialDec;
@@ -133,11 +119,12 @@ export async function issueAwsCredentials(input) {
133
119
  if (serial.length > 1 && serial[0] === 0x00) {
134
120
  serial = serial.slice(1);
135
121
  }
136
- let hex = "";
122
+ // Build BigInt directly from bytes (avoids hex string allocation)
123
+ let serialBig = 0n;
137
124
  for (let i = 0; i < serial.length; i++) {
138
- hex += HEX_TABLE[serial[i]];
125
+ serialBig = (serialBig << 8n) | BigInt(serial[i]);
139
126
  }
140
- certSerialDec = BigInt("0x" + hex).toString();
127
+ certSerialDec = serialBig.toString();
141
128
  cachedCertSerialDec = certSerialDec;
142
129
  cachedCertSerialSource = normalizedCert;
143
130
  }
@@ -146,6 +133,37 @@ export async function issueAwsCredentials(input) {
146
133
  throw new InternalError("invalid_cert_der");
147
134
  }
148
135
  }
136
+ // ---- STS credential cache lookup (using certificate serial) ----
137
+ cacheKey = `${region}|${roleArn}|${profileArn}|${certSerialDec}`;
138
+ const cachedEntry = stsCredentialCache.get(cacheKey);
139
+ // Only reuse cached credentials if they still have >1h1m remaining
140
+ const MIN_REMAINING_MS = 61 * 60 * 1000;
141
+ if (cachedEntry) {
142
+ // If credentials already resolved and still valid → reuse
143
+ if (cachedEntry.expiresAt > Date.now() + MIN_REMAINING_MS) {
144
+ return cachedEntry.promise;
145
+ }
146
+ // If a refresh is already in-flight → await same promise
147
+ if (cachedEntry.expiresAt === 0) {
148
+ return cachedEntry.promise;
149
+ }
150
+ }
151
+ const { signingKey } = await getSigningMaterial({
152
+ certBase64: normalizedCert,
153
+ privateKeyPkcs8Base64,
154
+ });
155
+ // 2. Setup constants
156
+ const host = `${SERVICE}.${region}.amazonaws.com`;
157
+ const path = PATH;
158
+ const now = new Date();
159
+ const amzDate = now.toISOString().replace(/[:-]|\.\d{3}/g, "");
160
+ const dateStamp = amzDate.slice(0, 8);
161
+ const service = SERVICE;
162
+ // 3. Chuẩn bị Body & Cert
163
+ const body = JSON.stringify({ trustAnchorArn, profileArn, roleArn, durationSeconds: sessionTtl });
164
+ const payloadHash = await getPayloadHash(body);
165
+ // --- REFACTOR: Làm sạch chứng chỉ chặt chẽ ---
166
+ // const normalizedCert = normalizeCert(certBase64);
149
167
  // 4. Tính toán Signature
150
168
  const baseHeaders = {
151
169
  "content-type": "application/json",
@@ -178,42 +196,58 @@ export async function issueAwsCredentials(input) {
178
196
  "X-Amz-X509": normalizedCert,
179
197
  "Authorization": `AWS4-X509-ECDSA-SHA256 Credential=${certSerialDec}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signatureHex}`
180
198
  });
181
- try {
182
- const res = await fetch(`https://${host}${path}`, {
183
- method: "POST",
184
- headers: finalHeaders,
185
- body,
186
- });
187
- if (!res.ok) {
188
- const errorBody = await res.text();
189
- // Debug logs
190
- console.error("[aws-rejected] Request details", {
191
- status: res.status,
192
- response: errorBody,
193
- region,
194
- profile,
195
- amzDate
199
+ const refreshPromise = (async () => {
200
+ try {
201
+ const res = await fetch(`https://${host}${path}`, {
202
+ method: "POST",
203
+ headers: finalHeaders,
204
+ body,
196
205
  });
197
- throw new InternalError("aws_rejected");
206
+ if (!res.ok) {
207
+ const errorBody = await res.text();
208
+ console.error("[aws-rejected] Request details", {
209
+ status: res.status,
210
+ response: errorBody,
211
+ region,
212
+ profile,
213
+ amzDate
214
+ });
215
+ throw new InternalError("aws_rejected");
216
+ }
217
+ const json = await res.json();
218
+ const creds = json?.credentialSet?.[0]?.credentials;
219
+ if (!creds?.accessKeyId || !creds?.secretAccessKey || !creds?.sessionToken) {
220
+ console.error("[issueAwsCredentials] malformed AWS credential response", { json });
221
+ throw new InternalError("aws_malformed_credentials");
222
+ }
223
+ const result = {
224
+ accessKeyId: creds.accessKeyId,
225
+ secretAccessKey: creds.secretAccessKey,
226
+ sessionToken: creds.sessionToken,
227
+ expiration: creds.expiration,
228
+ };
229
+ const exp = new Date(creds.expiration).getTime();
230
+ // update cache expiration after success
231
+ const entry = stsCredentialCache.get(cacheKey);
232
+ if (entry) {
233
+ entry.expiresAt = exp;
234
+ }
235
+ return result;
198
236
  }
199
- const json = await res.json();
200
- const creds = json?.credentialSet?.[0]?.credentials;
201
- if (!creds?.accessKeyId || !creds?.secretAccessKey || !creds?.sessionToken) {
202
- console.error("[issueAwsCredentials] malformed AWS credential response", { json });
203
- throw new InternalError("aws_malformed_credentials");
237
+ catch (e) {
238
+ // remove broken cache entry so next call can retry
239
+ stsCredentialCache.delete(cacheKey);
240
+ if (e instanceof InternalError)
241
+ throw e;
242
+ throw new InternalError("aws_unreachable");
204
243
  }
205
- return {
206
- accessKeyId: creds.accessKeyId,
207
- secretAccessKey: creds.secretAccessKey,
208
- sessionToken: creds.sessionToken,
209
- expiration: creds.expiration,
210
- };
211
- }
212
- catch (e) {
213
- if (e instanceof InternalError)
214
- throw e;
215
- throw new InternalError("aws_unreachable");
216
- }
244
+ })();
245
+ // store promise immediately to deduplicate concurrent refresh
246
+ stsCredentialCache.set(cacheKey, {
247
+ promise: refreshPromise,
248
+ expiresAt: 0,
249
+ });
250
+ return refreshPromise;
217
251
  }
218
252
  // ---- helpers ----
219
253
  function normalizeCert(raw) {
@@ -223,15 +257,14 @@ function normalizeCert(raw) {
223
257
  return raw.trim();
224
258
  }
225
259
  async function sha256Hex(input) {
226
- const data = textEncoder.encode(input);
227
- const hash = await crypto.subtle.digest("SHA-256", data);
260
+ // Tận dụng textEncoder có sẵn
261
+ const hash = await crypto.subtle.digest("SHA-256", textEncoder.encode(input));
228
262
  const bytes = new Uint8Array(hash);
229
- // build hex string with minimal allocations
230
- let out = "";
231
- for (let i = 0; i < bytes.length; i++) {
232
- out += HEX_TABLE[bytes[i]];
263
+ const hexParts = new Array(32); // SHA-256 luôn 32 bytes
264
+ for (let i = 0; i < 32; i++) {
265
+ hexParts[i] = HEX_TABLE[bytes[i]];
233
266
  }
234
- return out;
267
+ return hexParts.join("");
235
268
  }
236
269
  async function getCanonicalRequestHash(canonicalRequest) {
237
270
  return sha256Hex(canonicalRequest);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vizamodo/aws-sts-core",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "Pure AWS STS + SigV4 (X509 Roles Anywhere) core logic",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",