facturas 0.4.1 → 0.5.0
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.
- package/README.md +2 -0
- package/dist/errors.d.ts +27 -1
- package/dist/errors.js +25 -0
- package/dist/errors.js.map +1 -1
- package/dist/{index-BLq3d6xg.d.ts → index-CqXVyU0E.d.ts} +1 -1
- package/dist/index.d.ts +8 -6
- package/dist/index.js +434 -97
- package/dist/index.js.map +1 -1
- package/dist/padron.d.ts +2 -2
- package/dist/padron.js.map +1 -1
- package/dist/{types-SloiIQkT.d.ts → types-CKKe1Oo4.d.ts} +13 -1
- package/dist/types.d.ts +1 -1
- package/dist/wsfe.d.ts +12 -4
- package/dist/wsfe.js +61 -41
- package/dist/wsfe.js.map +1 -1
- package/dist/wsmtxca.d.ts +2 -2
- package/dist/wsmtxca.js.map +1 -1
- package/package.json +14 -15
package/dist/index.js
CHANGED
|
@@ -24,10 +24,12 @@ var ArcaInputError = class extends ArcaError {
|
|
|
24
24
|
var ArcaTransportError = class extends ArcaError {
|
|
25
25
|
name = "ArcaTransportError";
|
|
26
26
|
statusCode;
|
|
27
|
+
contentType;
|
|
27
28
|
responseBody;
|
|
28
29
|
constructor(message, options) {
|
|
29
30
|
super(message, "ARCA_TRANSPORT_ERROR", options);
|
|
30
31
|
this.statusCode = options?.statusCode;
|
|
32
|
+
this.contentType = options?.contentType;
|
|
31
33
|
this.responseBody = options?.responseBody;
|
|
32
34
|
}
|
|
33
35
|
};
|
|
@@ -41,6 +43,28 @@ var ArcaSoapFaultError = class extends ArcaError {
|
|
|
41
43
|
this.detail = options?.detail;
|
|
42
44
|
}
|
|
43
45
|
};
|
|
46
|
+
var ArcaInvalidSoapResponseError = class extends ArcaError {
|
|
47
|
+
name = "ArcaInvalidSoapResponseError";
|
|
48
|
+
service;
|
|
49
|
+
operation;
|
|
50
|
+
endpointUrl;
|
|
51
|
+
statusCode;
|
|
52
|
+
contentType;
|
|
53
|
+
responseBodyLength;
|
|
54
|
+
responseBodyPreview;
|
|
55
|
+
parsedDetail;
|
|
56
|
+
constructor(message, options) {
|
|
57
|
+
super(message, "ARCA_INVALID_SOAP_RESPONSE", options);
|
|
58
|
+
this.service = options?.service;
|
|
59
|
+
this.operation = options?.operation;
|
|
60
|
+
this.endpointUrl = options?.endpointUrl;
|
|
61
|
+
this.statusCode = options?.statusCode;
|
|
62
|
+
this.contentType = options?.contentType;
|
|
63
|
+
this.responseBodyLength = options?.responseBodyLength;
|
|
64
|
+
this.responseBodyPreview = options?.responseBodyPreview;
|
|
65
|
+
this.parsedDetail = options?.parsedDetail;
|
|
66
|
+
}
|
|
67
|
+
};
|
|
44
68
|
var ArcaServiceError = class extends ArcaError {
|
|
45
69
|
name = "ArcaServiceError";
|
|
46
70
|
serviceCode;
|
|
@@ -125,12 +149,33 @@ function assertArcaClientConfig(config) {
|
|
|
125
149
|
if (normalized.logger?.log !== void 0 && typeof normalized.logger.log !== "function") {
|
|
126
150
|
invalidFields.push("logger.log");
|
|
127
151
|
}
|
|
152
|
+
invalidFields.push(...getInvalidWsaaSessionStoreFields(normalized));
|
|
128
153
|
if (invalidFields.length > 0) {
|
|
129
154
|
throw new ArcaConfigurationError(
|
|
130
155
|
`Missing or invalid ARCA client config fields: ${invalidFields.join(", ")}`
|
|
131
156
|
);
|
|
132
157
|
}
|
|
133
158
|
}
|
|
159
|
+
function getInvalidWsaaSessionStoreFields(config) {
|
|
160
|
+
const store = config.wsaaSessionStore;
|
|
161
|
+
if (store === void 0) {
|
|
162
|
+
return [];
|
|
163
|
+
}
|
|
164
|
+
const invalidFields = [];
|
|
165
|
+
if (typeof store.get !== "function") {
|
|
166
|
+
invalidFields.push("wsaaSessionStore.get");
|
|
167
|
+
}
|
|
168
|
+
if (typeof store.set !== "function") {
|
|
169
|
+
invalidFields.push("wsaaSessionStore.set");
|
|
170
|
+
}
|
|
171
|
+
if (store.delete !== void 0 && typeof store.delete !== "function") {
|
|
172
|
+
invalidFields.push("wsaaSessionStore.delete");
|
|
173
|
+
}
|
|
174
|
+
if (store.withLock !== void 0 && typeof store.withLock !== "function") {
|
|
175
|
+
invalidFields.push("wsaaSessionStore.withLock");
|
|
176
|
+
}
|
|
177
|
+
return invalidFields;
|
|
178
|
+
}
|
|
134
179
|
var ARCA_WSAA_CONFIG = {
|
|
135
180
|
namespace: "http://wsaa.view.sua.dvadac.desein.afip.gov",
|
|
136
181
|
endpoint: {
|
|
@@ -208,7 +253,8 @@ function normalizeArcaClientConfig(config) {
|
|
|
208
253
|
...config.logger,
|
|
209
254
|
...normalizedLoggerLevel === void 0 ? {} : { level: normalizedLoggerLevel }
|
|
210
255
|
}
|
|
211
|
-
}
|
|
256
|
+
},
|
|
257
|
+
...config.wsaaSessionStore === void 0 ? {} : { wsaaSessionStore: config.wsaaSessionStore }
|
|
212
258
|
};
|
|
213
259
|
}
|
|
214
260
|
function normalizeEnvironmentValue(value) {
|
|
@@ -439,7 +485,64 @@ function createWsfeService(options) {
|
|
|
439
485
|
});
|
|
440
486
|
return getWsfeResultEntries(result, resultKey).map(mapWsfeCatalogEntry);
|
|
441
487
|
}
|
|
488
|
+
function authorizeVoucher({
|
|
489
|
+
representedTaxId,
|
|
490
|
+
data,
|
|
491
|
+
voucherNumber
|
|
492
|
+
}) {
|
|
493
|
+
const normalizedInput = normalizeWsfeVoucherInput(data);
|
|
494
|
+
return authorizeNormalizedVoucher({
|
|
495
|
+
representedTaxId,
|
|
496
|
+
data: normalizedInput,
|
|
497
|
+
voucherNumber
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
async function authorizeNormalizedVoucher({
|
|
501
|
+
representedTaxId,
|
|
502
|
+
data: normalizedInput,
|
|
503
|
+
voucherNumber
|
|
504
|
+
}) {
|
|
505
|
+
const requestData = mapWsfeVoucherInput(normalizedInput, voucherNumber);
|
|
506
|
+
const auth = await options.auth.login("wsfe", { representedTaxId });
|
|
507
|
+
const response = await options.soap.execute({
|
|
508
|
+
service: "wsfe",
|
|
509
|
+
operation: "FECAESolicitar",
|
|
510
|
+
body: {
|
|
511
|
+
Auth: createWsfeAuth(
|
|
512
|
+
representedTaxId ?? options.config.taxId,
|
|
513
|
+
auth.token,
|
|
514
|
+
auth.sign
|
|
515
|
+
),
|
|
516
|
+
FeCAEReq: {
|
|
517
|
+
FeCabReq: {
|
|
518
|
+
CantReg: 1,
|
|
519
|
+
PtoVta: normalizedInput.salesPoint,
|
|
520
|
+
CbteTipo: normalizedInput.voucherType
|
|
521
|
+
},
|
|
522
|
+
FeDetReq: {
|
|
523
|
+
FECAEDetRequest: requestData
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
const result = unwrapWsfeOperationResult("FECAESolicitar", response.result);
|
|
529
|
+
const detailResponse = normalizeWsfeDetailResponse(result);
|
|
530
|
+
const cae = detailResponse.CAE;
|
|
531
|
+
const caeExpiry = detailResponse.CAEFchVto;
|
|
532
|
+
if (typeof cae !== "string" || typeof caeExpiry !== "string") {
|
|
533
|
+
throw new ArcaServiceError("WSFE did not return CAE authorization data", {
|
|
534
|
+
detail: result
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
return {
|
|
538
|
+
cae,
|
|
539
|
+
caeExpiry: String(caeExpiry),
|
|
540
|
+
voucherNumber,
|
|
541
|
+
raw: result
|
|
542
|
+
};
|
|
543
|
+
}
|
|
442
544
|
return {
|
|
545
|
+
authorizeVoucher,
|
|
443
546
|
async createNextVoucher({ representedTaxId, data }) {
|
|
444
547
|
const normalizedInput = normalizeWsfeVoucherInput(data);
|
|
445
548
|
const voucherNumber = await getNextVoucherNumber({
|
|
@@ -447,48 +550,11 @@ function createWsfeService(options) {
|
|
|
447
550
|
salesPoint: normalizedInput.salesPoint,
|
|
448
551
|
voucherType: normalizedInput.voucherType
|
|
449
552
|
});
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
operation: "FECAESolicitar",
|
|
455
|
-
body: {
|
|
456
|
-
Auth: createWsfeAuth(
|
|
457
|
-
representedTaxId ?? options.config.taxId,
|
|
458
|
-
auth.token,
|
|
459
|
-
auth.sign
|
|
460
|
-
),
|
|
461
|
-
FeCAEReq: {
|
|
462
|
-
FeCabReq: {
|
|
463
|
-
CantReg: 1,
|
|
464
|
-
PtoVta: normalizedInput.salesPoint,
|
|
465
|
-
CbteTipo: normalizedInput.voucherType
|
|
466
|
-
},
|
|
467
|
-
FeDetReq: {
|
|
468
|
-
FECAEDetRequest: requestData
|
|
469
|
-
}
|
|
470
|
-
}
|
|
471
|
-
}
|
|
553
|
+
return authorizeNormalizedVoucher({
|
|
554
|
+
representedTaxId,
|
|
555
|
+
data: normalizedInput,
|
|
556
|
+
voucherNumber
|
|
472
557
|
});
|
|
473
|
-
const result = unwrapWsfeOperationResult(
|
|
474
|
-
"FECAESolicitar",
|
|
475
|
-
response.result
|
|
476
|
-
);
|
|
477
|
-
const detailResponse = normalizeWsfeDetailResponse(result);
|
|
478
|
-
const cae = detailResponse.CAE;
|
|
479
|
-
const caeExpiry = detailResponse.CAEFchVto;
|
|
480
|
-
if (typeof cae !== "string" || typeof caeExpiry !== "string") {
|
|
481
|
-
throw new ArcaServiceError(
|
|
482
|
-
"WSFE did not return CAE authorization data",
|
|
483
|
-
{ detail: result }
|
|
484
|
-
);
|
|
485
|
-
}
|
|
486
|
-
return {
|
|
487
|
-
cae,
|
|
488
|
-
caeExpiry: String(caeExpiry),
|
|
489
|
-
voucherNumber,
|
|
490
|
-
raw: result
|
|
491
|
-
};
|
|
492
558
|
},
|
|
493
559
|
getNextVoucherNumber,
|
|
494
560
|
getLastVoucher(input) {
|
|
@@ -1199,7 +1265,7 @@ var legacyTlsAgent = new https.Agent({
|
|
|
1199
1265
|
keepAlive: true,
|
|
1200
1266
|
ciphers: "DEFAULT@SECLEVEL=0"
|
|
1201
1267
|
});
|
|
1202
|
-
async function
|
|
1268
|
+
async function postXmlWithMetadata({
|
|
1203
1269
|
url,
|
|
1204
1270
|
body,
|
|
1205
1271
|
contentType,
|
|
@@ -1267,12 +1333,12 @@ async function postXmlOnce({
|
|
|
1267
1333
|
const requestBody = Buffer.from(body, "utf8");
|
|
1268
1334
|
return await new Promise((resolve, reject) => {
|
|
1269
1335
|
let settled = false;
|
|
1270
|
-
const settleResolve = (
|
|
1336
|
+
const settleResolve = (response) => {
|
|
1271
1337
|
if (settled) {
|
|
1272
1338
|
return;
|
|
1273
1339
|
}
|
|
1274
1340
|
settled = true;
|
|
1275
|
-
resolve(
|
|
1341
|
+
resolve(response);
|
|
1276
1342
|
};
|
|
1277
1343
|
const settleReject = (error) => {
|
|
1278
1344
|
if (settled) {
|
|
@@ -1331,11 +1397,19 @@ async function postXmlOnce({
|
|
|
1331
1397
|
response.headers["content-type"]
|
|
1332
1398
|
) ? response.headers["content-type"].join("; ") : response.headers["content-type"];
|
|
1333
1399
|
if (statusCode >= 200 && statusCode < 300) {
|
|
1334
|
-
settleResolve(
|
|
1400
|
+
settleResolve({
|
|
1401
|
+
body: responseBody,
|
|
1402
|
+
statusCode,
|
|
1403
|
+
contentType: responseContentType
|
|
1404
|
+
});
|
|
1335
1405
|
return;
|
|
1336
1406
|
}
|
|
1337
1407
|
if (isXmlLikeResponse(responseBody, responseContentType)) {
|
|
1338
|
-
settleResolve(
|
|
1408
|
+
settleResolve({
|
|
1409
|
+
body: responseBody,
|
|
1410
|
+
statusCode,
|
|
1411
|
+
contentType: responseContentType
|
|
1412
|
+
});
|
|
1339
1413
|
return;
|
|
1340
1414
|
}
|
|
1341
1415
|
settleReject(
|
|
@@ -1343,6 +1417,7 @@ async function postXmlOnce({
|
|
|
1343
1417
|
`ARCA HTTP request failed with status ${statusCode}`,
|
|
1344
1418
|
{
|
|
1345
1419
|
statusCode,
|
|
1420
|
+
contentType: responseContentType,
|
|
1346
1421
|
responseBody
|
|
1347
1422
|
}
|
|
1348
1423
|
)
|
|
@@ -1417,14 +1492,26 @@ function buildSoapEnvelope(soapVersion, operation, namespace, body, options) {
|
|
|
1417
1492
|
};
|
|
1418
1493
|
return `<?xml version="1.0" encoding="utf-8"?>${xmlBuilder.build(payload)}`;
|
|
1419
1494
|
}
|
|
1420
|
-
function parseSoapBody(xml) {
|
|
1421
|
-
|
|
1495
|
+
function parseSoapBody(xml, context = {}) {
|
|
1496
|
+
let parsed;
|
|
1497
|
+
try {
|
|
1498
|
+
parsed = xmlParser.parse(xml);
|
|
1499
|
+
} catch (error) {
|
|
1500
|
+
throw createInvalidSoapResponseError(
|
|
1501
|
+
"Invalid SOAP response: XML parse failed",
|
|
1502
|
+
context,
|
|
1503
|
+
void 0,
|
|
1504
|
+
error instanceof Error ? error : void 0
|
|
1505
|
+
);
|
|
1506
|
+
}
|
|
1422
1507
|
const envelope = parsed.Envelope;
|
|
1423
1508
|
const body = envelope?.Body;
|
|
1424
1509
|
if (!body) {
|
|
1425
|
-
throw
|
|
1426
|
-
|
|
1427
|
-
|
|
1510
|
+
throw createInvalidSoapResponseError(
|
|
1511
|
+
"Invalid SOAP response: missing body",
|
|
1512
|
+
context,
|
|
1513
|
+
parsed
|
|
1514
|
+
);
|
|
1428
1515
|
}
|
|
1429
1516
|
const fault = body.Fault;
|
|
1430
1517
|
if (fault) {
|
|
@@ -1432,18 +1519,40 @@ function parseSoapBody(xml) {
|
|
|
1432
1519
|
}
|
|
1433
1520
|
return body;
|
|
1434
1521
|
}
|
|
1435
|
-
function getSingleBodyEntry(body) {
|
|
1522
|
+
function getSingleBodyEntry(body, context = {}) {
|
|
1436
1523
|
const entries = Object.entries(body).filter(([key]) => key !== "@_xmlns");
|
|
1437
1524
|
if (entries.length !== 1) {
|
|
1438
|
-
throw
|
|
1525
|
+
throw createInvalidSoapResponseError(
|
|
1439
1526
|
`Invalid SOAP response: expected a single body entry, got ${entries.length}`,
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
}
|
|
1527
|
+
context,
|
|
1528
|
+
body
|
|
1443
1529
|
);
|
|
1444
1530
|
}
|
|
1445
1531
|
return entries[0];
|
|
1446
1532
|
}
|
|
1533
|
+
function createInvalidSoapResponseError(message, context, parsedDetail, cause) {
|
|
1534
|
+
const responseBody = context.responseBody ?? "";
|
|
1535
|
+
return new ArcaInvalidSoapResponseError(message, {
|
|
1536
|
+
cause,
|
|
1537
|
+
service: context.service,
|
|
1538
|
+
operation: context.operation,
|
|
1539
|
+
endpointUrl: context.endpointUrl,
|
|
1540
|
+
statusCode: context.statusCode,
|
|
1541
|
+
contentType: context.contentType,
|
|
1542
|
+
responseBodyLength: responseBody.length,
|
|
1543
|
+
responseBodyPreview: sanitizeSoapResponsePreview(
|
|
1544
|
+
responseBody,
|
|
1545
|
+
context.responseBodyPreviewLength
|
|
1546
|
+
),
|
|
1547
|
+
parsedDetail
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
function sanitizeSoapResponsePreview(responseBody, maxLength = 4096) {
|
|
1551
|
+
return responseBody.replace(
|
|
1552
|
+
/<((?:[A-Za-z_][\w.-]*:)?(?:Token|Sign))\b([^>]*)>[\s\S]*?<\/\1>/gi,
|
|
1553
|
+
(_match, tagName, attributes) => `<${tagName}${attributes}>[REDACTED]</${tagName}>`
|
|
1554
|
+
).slice(0, maxLength);
|
|
1555
|
+
}
|
|
1447
1556
|
function parseXmlDocument(xml) {
|
|
1448
1557
|
return xmlParser.parse(xml);
|
|
1449
1558
|
}
|
|
@@ -1502,7 +1611,7 @@ function createSoapTransport(options) {
|
|
|
1502
1611
|
url
|
|
1503
1612
|
});
|
|
1504
1613
|
try {
|
|
1505
|
-
const
|
|
1614
|
+
const response = await postXmlWithMetadata({
|
|
1506
1615
|
url,
|
|
1507
1616
|
body: xml,
|
|
1508
1617
|
contentType,
|
|
@@ -1520,12 +1629,23 @@ function createSoapTransport(options) {
|
|
|
1520
1629
|
operation: request.operation,
|
|
1521
1630
|
durationMs: Date.now() - startedAt
|
|
1522
1631
|
});
|
|
1523
|
-
const
|
|
1524
|
-
|
|
1632
|
+
const parseContext = {
|
|
1633
|
+
service: request.service,
|
|
1634
|
+
operation: request.operation,
|
|
1635
|
+
endpointUrl: url,
|
|
1636
|
+
statusCode: response.statusCode,
|
|
1637
|
+
contentType: response.contentType,
|
|
1638
|
+
responseBody: response.body
|
|
1639
|
+
};
|
|
1640
|
+
const soapBody = parseSoapBody(response.body, parseContext);
|
|
1641
|
+
const [, result] = getSingleBodyEntry(
|
|
1642
|
+
soapBody,
|
|
1643
|
+
parseContext
|
|
1644
|
+
);
|
|
1525
1645
|
return {
|
|
1526
1646
|
service: request.service,
|
|
1527
1647
|
operation: request.operation,
|
|
1528
|
-
raw:
|
|
1648
|
+
raw: response.body,
|
|
1529
1649
|
result
|
|
1530
1650
|
};
|
|
1531
1651
|
} catch (error) {
|
|
@@ -1538,6 +1658,17 @@ function createSoapTransport(options) {
|
|
|
1538
1658
|
error
|
|
1539
1659
|
});
|
|
1540
1660
|
}
|
|
1661
|
+
if (error instanceof ArcaInvalidSoapResponseError) {
|
|
1662
|
+
options.logger?.error("ARCA invalid SOAP response", {
|
|
1663
|
+
service: request.service,
|
|
1664
|
+
operation: request.operation,
|
|
1665
|
+
url,
|
|
1666
|
+
statusCode: error.statusCode,
|
|
1667
|
+
contentType: error.contentType,
|
|
1668
|
+
responseBodyLength: error.responseBodyLength,
|
|
1669
|
+
error
|
|
1670
|
+
});
|
|
1671
|
+
}
|
|
1541
1672
|
throw error;
|
|
1542
1673
|
}
|
|
1543
1674
|
}
|
|
@@ -1547,48 +1678,117 @@ function createSoapTransport(options) {
|
|
|
1547
1678
|
// src/wsaa/index.ts
|
|
1548
1679
|
import { createHash } from "crypto";
|
|
1549
1680
|
import forge from "node-forge";
|
|
1681
|
+
|
|
1682
|
+
// src/wsaa/session-store.ts
|
|
1683
|
+
var CREDENTIAL_EXPIRY_SAFETY_MARGIN_MS = 6e4;
|
|
1684
|
+
function createMemoryWsaaSessionStore() {
|
|
1685
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
1686
|
+
const locks = /* @__PURE__ */ new Map();
|
|
1687
|
+
return {
|
|
1688
|
+
get(key) {
|
|
1689
|
+
const credentials = sessions.get(serializeWsaaSessionKey(key));
|
|
1690
|
+
if (!(credentials && isWsaaCredentialValid(credentials))) {
|
|
1691
|
+
return Promise.resolve(null);
|
|
1692
|
+
}
|
|
1693
|
+
return Promise.resolve({ ...credentials });
|
|
1694
|
+
},
|
|
1695
|
+
set(key, credentials) {
|
|
1696
|
+
sessions.set(serializeWsaaSessionKey(key), { ...credentials });
|
|
1697
|
+
return Promise.resolve();
|
|
1698
|
+
},
|
|
1699
|
+
delete(key) {
|
|
1700
|
+
sessions.delete(serializeWsaaSessionKey(key));
|
|
1701
|
+
return Promise.resolve();
|
|
1702
|
+
},
|
|
1703
|
+
async withLock(key, fn) {
|
|
1704
|
+
const lockKey = serializeWsaaSessionKey(key);
|
|
1705
|
+
const previous = locks.get(lockKey) ?? Promise.resolve();
|
|
1706
|
+
let release = () => void 0;
|
|
1707
|
+
const current = new Promise((resolve) => {
|
|
1708
|
+
release = resolve;
|
|
1709
|
+
});
|
|
1710
|
+
const queued = previous.catch(() => void 0).then(() => current);
|
|
1711
|
+
locks.set(lockKey, queued);
|
|
1712
|
+
await previous.catch(() => void 0);
|
|
1713
|
+
try {
|
|
1714
|
+
return await fn();
|
|
1715
|
+
} finally {
|
|
1716
|
+
release();
|
|
1717
|
+
if (locks.get(lockKey) === queued) {
|
|
1718
|
+
locks.delete(lockKey);
|
|
1719
|
+
}
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
}
|
|
1724
|
+
function serializeWsaaSessionKey(key) {
|
|
1725
|
+
return [key.environment, key.service, key.certificateFingerprint].join(":");
|
|
1726
|
+
}
|
|
1727
|
+
function isWsaaCredentialValid(credentials) {
|
|
1728
|
+
return new Date(credentials.expiresAt).getTime() - Date.now() > CREDENTIAL_EXPIRY_SAFETY_MARGIN_MS;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// src/wsaa/index.ts
|
|
1550
1732
|
function createWsaaAuthModule(options) {
|
|
1551
1733
|
const cache = /* @__PURE__ */ new Map();
|
|
1552
1734
|
const inFlight = /* @__PURE__ */ new Map();
|
|
1553
1735
|
return {
|
|
1554
1736
|
async login(service, authOptions = {}) {
|
|
1555
|
-
const
|
|
1737
|
+
const sessionKey = buildWsaaSessionKey(options.config, service);
|
|
1738
|
+
const cacheKey = serializeWsaaSessionKey(sessionKey);
|
|
1556
1739
|
const running = inFlight.get(cacheKey);
|
|
1557
1740
|
if (running) {
|
|
1558
1741
|
return running;
|
|
1559
1742
|
}
|
|
1560
1743
|
const loginPromise = (async () => {
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
});
|
|
1568
|
-
return cached;
|
|
1569
|
-
}
|
|
1570
|
-
}
|
|
1571
|
-
options.logger?.debug("Attempting WSAA login", {
|
|
1744
|
+
const reuse = await getReusableCredentials({
|
|
1745
|
+
config: options.config,
|
|
1746
|
+
cache,
|
|
1747
|
+
cacheKey,
|
|
1748
|
+
sessionKey,
|
|
1749
|
+
logger: options.logger,
|
|
1572
1750
|
service,
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
const credentials = await requestCredentials(options.config, service, {
|
|
1576
|
-
logger: options.logger
|
|
1751
|
+
allowStore: !authOptions.forceRefresh,
|
|
1752
|
+
allowCache: !authOptions.forceRefresh
|
|
1577
1753
|
});
|
|
1578
|
-
|
|
1754
|
+
if (reuse) {
|
|
1755
|
+
return reuse;
|
|
1756
|
+
}
|
|
1757
|
+
const refresh = () => refreshWsaaCredentials({
|
|
1758
|
+
config: options.config,
|
|
1759
|
+
cache,
|
|
1760
|
+
cacheKey,
|
|
1761
|
+
sessionKey,
|
|
1762
|
+
logger: options.logger,
|
|
1579
1763
|
service,
|
|
1580
|
-
|
|
1764
|
+
forceRefresh: authOptions.forceRefresh === true
|
|
1581
1765
|
});
|
|
1582
|
-
|
|
1583
|
-
|
|
1766
|
+
if (options.config.wsaaSessionStore?.withLock) {
|
|
1767
|
+
return await withWsaaSessionStoreLock(
|
|
1768
|
+
options.config,
|
|
1769
|
+
sessionKey,
|
|
1770
|
+
service,
|
|
1771
|
+
refresh
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
return await refresh();
|
|
1584
1775
|
})();
|
|
1585
1776
|
inFlight.set(cacheKey, loginPromise);
|
|
1586
1777
|
try {
|
|
1587
1778
|
return await loginPromise;
|
|
1588
1779
|
} catch (error) {
|
|
1589
|
-
if (
|
|
1590
|
-
const
|
|
1591
|
-
|
|
1780
|
+
if (error instanceof ArcaSoapFaultError && error.faultCode === "ns1:coe.alreadyAuthenticated") {
|
|
1781
|
+
const recovered = await getReusableCredentials({
|
|
1782
|
+
config: options.config,
|
|
1783
|
+
cache,
|
|
1784
|
+
cacheKey,
|
|
1785
|
+
sessionKey,
|
|
1786
|
+
logger: options.logger,
|
|
1787
|
+
service,
|
|
1788
|
+
allowStore: true,
|
|
1789
|
+
allowCache: true
|
|
1790
|
+
});
|
|
1791
|
+
if (recovered) {
|
|
1592
1792
|
options.logger?.warn(
|
|
1593
1793
|
"Recovered WSAA coe.alreadyAuthenticated fault",
|
|
1594
1794
|
{
|
|
@@ -1596,7 +1796,13 @@ function createWsaaAuthModule(options) {
|
|
|
1596
1796
|
faultCode: error.faultCode
|
|
1597
1797
|
}
|
|
1598
1798
|
);
|
|
1599
|
-
return
|
|
1799
|
+
return recovered;
|
|
1800
|
+
}
|
|
1801
|
+
if (!options.config.wsaaSessionStore) {
|
|
1802
|
+
throw new ArcaConfigurationError(
|
|
1803
|
+
"WSAA login failed because another process likely owns a valid TA. Configure a durable wsaaSessionStore for multi-process or serverless deployments.",
|
|
1804
|
+
{ cause: error }
|
|
1805
|
+
);
|
|
1600
1806
|
}
|
|
1601
1807
|
}
|
|
1602
1808
|
if (error instanceof ArcaSoapFaultError) {
|
|
@@ -1627,7 +1833,8 @@ async function requestCredentials(config, service, options) {
|
|
|
1627
1833
|
ARCA_WSAA_CONFIG.namespace,
|
|
1628
1834
|
{ in0: signedCms }
|
|
1629
1835
|
);
|
|
1630
|
-
const
|
|
1836
|
+
const url = ARCA_WSAA_CONFIG.endpoint[config.environment];
|
|
1837
|
+
const response = await postXmlWithMetadata({
|
|
1631
1838
|
url: ARCA_WSAA_CONFIG.endpoint[config.environment],
|
|
1632
1839
|
body: requestXml,
|
|
1633
1840
|
contentType: 'text/xml; charset="utf-8"',
|
|
@@ -1639,9 +1846,20 @@ async function requestCredentials(config, service, options) {
|
|
|
1639
1846
|
service: "wsaa",
|
|
1640
1847
|
operation: "loginCms"
|
|
1641
1848
|
});
|
|
1642
|
-
const
|
|
1643
|
-
|
|
1644
|
-
|
|
1849
|
+
const parseContext = {
|
|
1850
|
+
service: "wsaa",
|
|
1851
|
+
operation: "loginCms",
|
|
1852
|
+
endpointUrl: url,
|
|
1853
|
+
statusCode: response.statusCode,
|
|
1854
|
+
contentType: response.contentType,
|
|
1855
|
+
responseBody: response.body
|
|
1856
|
+
};
|
|
1857
|
+
const soapBody = parseSoapBody(response.body, parseContext);
|
|
1858
|
+
const [, responseBody] = getSingleBodyEntry(
|
|
1859
|
+
soapBody,
|
|
1860
|
+
parseContext
|
|
1861
|
+
);
|
|
1862
|
+
const loginCmsReturn = responseBody.loginCmsReturn;
|
|
1645
1863
|
if (typeof loginCmsReturn !== "string" || loginCmsReturn.trim().length < 1) {
|
|
1646
1864
|
throw new ArcaTransportError(
|
|
1647
1865
|
"WSAA response did not include loginCmsReturn XML"
|
|
@@ -1649,24 +1867,141 @@ async function requestCredentials(config, service, options) {
|
|
|
1649
1867
|
}
|
|
1650
1868
|
return parseLoginTicketResponse(loginCmsReturn);
|
|
1651
1869
|
}
|
|
1652
|
-
function
|
|
1653
|
-
return
|
|
1654
|
-
|
|
1655
|
-
|
|
1870
|
+
function buildWsaaSessionKey(config, service) {
|
|
1871
|
+
return {
|
|
1872
|
+
environment: config.environment,
|
|
1873
|
+
service,
|
|
1874
|
+
certificateFingerprint: getCertificateFingerprint(config)
|
|
1875
|
+
};
|
|
1656
1876
|
}
|
|
1657
1877
|
function getCertificateFingerprint(config) {
|
|
1658
1878
|
return createHash("sha256").update(config.certificatePem).digest("hex");
|
|
1659
1879
|
}
|
|
1660
|
-
function isCredentialValid(credentials) {
|
|
1661
|
-
return new Date(credentials.expiresAt).getTime() - Date.now() > 6e4;
|
|
1662
|
-
}
|
|
1663
1880
|
function getCachedCredentials(cache, cacheKey) {
|
|
1664
1881
|
const localCached = cache.get(cacheKey);
|
|
1665
|
-
if (localCached &&
|
|
1882
|
+
if (localCached && isWsaaCredentialValid(localCached)) {
|
|
1666
1883
|
return localCached;
|
|
1667
1884
|
}
|
|
1668
1885
|
return null;
|
|
1669
1886
|
}
|
|
1887
|
+
async function getReusableCredentials(options) {
|
|
1888
|
+
if (options.allowCache) {
|
|
1889
|
+
const cached = getCachedCredentials(options.cache, options.cacheKey);
|
|
1890
|
+
if (cached) {
|
|
1891
|
+
options.logger?.debug("Attempting WSAA login", {
|
|
1892
|
+
service: options.service,
|
|
1893
|
+
source: "cached"
|
|
1894
|
+
});
|
|
1895
|
+
return cached;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
if (!(options.allowStore && options.config.wsaaSessionStore)) {
|
|
1899
|
+
return null;
|
|
1900
|
+
}
|
|
1901
|
+
const stored = await getStoredCredentials(
|
|
1902
|
+
options.config,
|
|
1903
|
+
options.sessionKey,
|
|
1904
|
+
options.service
|
|
1905
|
+
);
|
|
1906
|
+
if (!stored) {
|
|
1907
|
+
return null;
|
|
1908
|
+
}
|
|
1909
|
+
options.cache.set(options.cacheKey, stored);
|
|
1910
|
+
options.logger?.debug("Attempting WSAA login", {
|
|
1911
|
+
service: options.service,
|
|
1912
|
+
source: "store"
|
|
1913
|
+
});
|
|
1914
|
+
return stored;
|
|
1915
|
+
}
|
|
1916
|
+
async function refreshWsaaCredentials(options) {
|
|
1917
|
+
if (!options.forceRefresh) {
|
|
1918
|
+
const reuse = await getReusableCredentials({
|
|
1919
|
+
config: options.config,
|
|
1920
|
+
cache: options.cache,
|
|
1921
|
+
cacheKey: options.cacheKey,
|
|
1922
|
+
sessionKey: options.sessionKey,
|
|
1923
|
+
logger: options.logger,
|
|
1924
|
+
service: options.service,
|
|
1925
|
+
allowStore: true,
|
|
1926
|
+
allowCache: true
|
|
1927
|
+
});
|
|
1928
|
+
if (reuse) {
|
|
1929
|
+
return reuse;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
options.logger?.debug("Attempting WSAA login", {
|
|
1933
|
+
service: options.service,
|
|
1934
|
+
source: "fresh"
|
|
1935
|
+
});
|
|
1936
|
+
const credentials = await requestCredentials(
|
|
1937
|
+
options.config,
|
|
1938
|
+
options.service,
|
|
1939
|
+
{
|
|
1940
|
+
logger: options.logger
|
|
1941
|
+
}
|
|
1942
|
+
);
|
|
1943
|
+
options.logger?.info("WSAA login succeeded", {
|
|
1944
|
+
service: options.service,
|
|
1945
|
+
expiresAt: credentials.expiresAt
|
|
1946
|
+
});
|
|
1947
|
+
options.cache.set(options.cacheKey, credentials);
|
|
1948
|
+
await setStoredCredentials(options.config, options.sessionKey, credentials);
|
|
1949
|
+
return credentials;
|
|
1950
|
+
}
|
|
1951
|
+
async function getStoredCredentials(config, key, service) {
|
|
1952
|
+
if (!config.wsaaSessionStore) {
|
|
1953
|
+
return null;
|
|
1954
|
+
}
|
|
1955
|
+
try {
|
|
1956
|
+
const credentials = await config.wsaaSessionStore.get(key);
|
|
1957
|
+
if (!(credentials && isWsaaCredentialValid(credentials))) {
|
|
1958
|
+
return null;
|
|
1959
|
+
}
|
|
1960
|
+
return credentials;
|
|
1961
|
+
} catch (error) {
|
|
1962
|
+
throw new ArcaConfigurationError(
|
|
1963
|
+
`WSAA session store get failed for service ${service}`,
|
|
1964
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
1965
|
+
);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
async function setStoredCredentials(config, key, credentials) {
|
|
1969
|
+
if (!config.wsaaSessionStore) {
|
|
1970
|
+
return;
|
|
1971
|
+
}
|
|
1972
|
+
try {
|
|
1973
|
+
await config.wsaaSessionStore.set(key, credentials);
|
|
1974
|
+
} catch (error) {
|
|
1975
|
+
throw new ArcaConfigurationError(
|
|
1976
|
+
`WSAA session store set failed for service ${key.service}`,
|
|
1977
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
1978
|
+
);
|
|
1979
|
+
}
|
|
1980
|
+
}
|
|
1981
|
+
async function withWsaaSessionStoreLock(config, key, service, fn) {
|
|
1982
|
+
const store = config.wsaaSessionStore;
|
|
1983
|
+
if (!store?.withLock) {
|
|
1984
|
+
return await fn();
|
|
1985
|
+
}
|
|
1986
|
+
let entered = false;
|
|
1987
|
+
try {
|
|
1988
|
+
return await store.withLock(key, async () => {
|
|
1989
|
+
entered = true;
|
|
1990
|
+
return await fn();
|
|
1991
|
+
});
|
|
1992
|
+
} catch (error) {
|
|
1993
|
+
if (entered) {
|
|
1994
|
+
throw error;
|
|
1995
|
+
}
|
|
1996
|
+
if (error instanceof ArcaConfigurationError) {
|
|
1997
|
+
throw error;
|
|
1998
|
+
}
|
|
1999
|
+
throw new ArcaConfigurationError(
|
|
2000
|
+
`WSAA session store lock failed for service ${service}`,
|
|
2001
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
2002
|
+
);
|
|
2003
|
+
}
|
|
2004
|
+
}
|
|
1670
2005
|
function buildLoginTicketRequest(service) {
|
|
1671
2006
|
const uniqueId = Math.floor(Date.now() / 1e3);
|
|
1672
2007
|
const generationTime = new Date(Date.now() - 5 * 6e4).toISOString().replace(".000Z", "Z");
|
|
@@ -1751,12 +2086,14 @@ export {
|
|
|
1751
2086
|
ArcaConfigurationError,
|
|
1752
2087
|
ArcaError,
|
|
1753
2088
|
ArcaInputError,
|
|
2089
|
+
ArcaInvalidSoapResponseError,
|
|
1754
2090
|
ArcaServiceError,
|
|
1755
2091
|
ArcaSoapFaultError,
|
|
1756
2092
|
ArcaTransportError,
|
|
1757
2093
|
assertArcaClientConfig,
|
|
1758
2094
|
createArcaClient,
|
|
1759
2095
|
createArcaClientConfigFromEnv,
|
|
2096
|
+
createMemoryWsaaSessionStore,
|
|
1760
2097
|
createPadronService,
|
|
1761
2098
|
createWsfeService,
|
|
1762
2099
|
createWsmtxcaService,
|