@open-nav/core 0.1.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/LICENSE +29 -0
- package/README.md +132 -0
- package/dist/constants.d.ts +49 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +58 -0
- package/dist/constants.js.map +1 -0
- package/dist/crypto/hash.d.ts +16 -0
- package/dist/crypto/hash.d.ts.map +1 -0
- package/dist/crypto/hash.js +30 -0
- package/dist/crypto/hash.js.map +1 -0
- package/dist/crypto/node-provider.d.ts +9 -0
- package/dist/crypto/node-provider.d.ts.map +1 -0
- package/dist/crypto/node-provider.js +18 -0
- package/dist/crypto/node-provider.js.map +1 -0
- package/dist/crypto/provider.d.ts +28 -0
- package/dist/crypto/provider.d.ts.map +1 -0
- package/dist/crypto/provider.js +14 -0
- package/dist/crypto/provider.js.map +1 -0
- package/dist/crypto/request-id.d.ts +16 -0
- package/dist/crypto/request-id.d.ts.map +1 -0
- package/dist/crypto/request-id.js +59 -0
- package/dist/crypto/request-id.js.map +1 -0
- package/dist/crypto/signature.d.ts +40 -0
- package/dist/crypto/signature.d.ts.map +1 -0
- package/dist/crypto/signature.js +38 -0
- package/dist/crypto/signature.js.map +1 -0
- package/dist/crypto/token.d.ts +14 -0
- package/dist/crypto/token.d.ts.map +1 -0
- package/dist/crypto/token.js +82 -0
- package/dist/crypto/token.js.map +1 -0
- package/dist/crypto/web-provider.d.ts +18 -0
- package/dist/crypto/web-provider.d.ts.map +1 -0
- package/dist/crypto/web-provider.js +31 -0
- package/dist/crypto/web-provider.js.map +1 -0
- package/dist/errors.d.ts +75 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +71 -0
- package/dist/errors.js.map +1 -0
- package/dist/generated/fault-codes.d.ts +1343 -0
- package/dist/generated/fault-codes.d.ts.map +1 -0
- package/dist/generated/fault-codes.js +1331 -0
- package/dist/generated/fault-codes.js.map +1 -0
- package/dist/generated/index.d.ts +4 -0
- package/dist/generated/index.d.ts.map +1 -0
- package/dist/generated/index.js +5 -0
- package/dist/generated/index.js.map +1 -0
- package/dist/generated/schema.d.ts +13 -0
- package/dist/generated/schema.d.ts.map +1 -0
- package/dist/generated/schema.js +3756 -0
- package/dist/generated/schema.js.map +1 -0
- package/dist/generated/types.d.ts +5580 -0
- package/dist/generated/types.d.ts.map +1 -0
- package/dist/generated/types.js +6 -0
- package/dist/generated/types.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/invoice/payload.d.ts +40 -0
- package/dist/invoice/payload.d.ts.map +1 -0
- package/dist/invoice/payload.js +66 -0
- package/dist/invoice/payload.js.map +1 -0
- package/dist/money/decimal.d.ts +69 -0
- package/dist/money/decimal.d.ts.map +1 -0
- package/dist/money/decimal.js +190 -0
- package/dist/money/decimal.js.map +1 -0
- package/dist/money/summary.d.ts +60 -0
- package/dist/money/summary.d.ts.map +1 -0
- package/dist/money/summary.js +236 -0
- package/dist/money/summary.js.map +1 -0
- package/dist/time.d.ts +16 -0
- package/dist/time.d.ts.map +1 -0
- package/dist/time.js +41 -0
- package/dist/time.js.map +1 -0
- package/dist/validation/issue.d.ts +67 -0
- package/dist/validation/issue.d.ts.map +1 -0
- package/dist/validation/issue.js +40 -0
- package/dist/validation/issue.js.map +1 -0
- package/dist/validation/rules.d.ts +34 -0
- package/dist/validation/rules.d.ts.map +1 -0
- package/dist/validation/rules.js +290 -0
- package/dist/validation/rules.js.map +1 -0
- package/dist/validation/schema.d.ts +14 -0
- package/dist/validation/schema.d.ts.map +1 -0
- package/dist/validation/schema.js +231 -0
- package/dist/validation/schema.js.map +1 -0
- package/dist/validation/tax-number.d.ts +39 -0
- package/dist/validation/tax-number.d.ts.map +1 -0
- package/dist/validation/tax-number.js +79 -0
- package/dist/validation/tax-number.js.map +1 -0
- package/dist/validation/validate.d.ts +29 -0
- package/dist/validation/validate.d.ts.map +1 -0
- package/dist/validation/validate.js +33 -0
- package/dist/validation/validate.js.map +1 -0
- package/dist/xml/descriptor.d.ts +82 -0
- package/dist/xml/descriptor.d.ts.map +1 -0
- package/dist/xml/descriptor.js +32 -0
- package/dist/xml/descriptor.js.map +1 -0
- package/dist/xml/read.d.ts +25 -0
- package/dist/xml/read.d.ts.map +1 -0
- package/dist/xml/read.js +230 -0
- package/dist/xml/read.js.map +1 -0
- package/dist/xml/write.d.ts +28 -0
- package/dist/xml/write.d.ts.map +1 -0
- package/dist/xml/write.js +299 -0
- package/dist/xml/write.js.map +1 -0
- package/package.json +55 -0
- package/src/constants.ts +76 -0
- package/src/crypto/hash.ts +35 -0
- package/src/crypto/node-provider.ts +19 -0
- package/src/crypto/provider.ts +37 -0
- package/src/crypto/request-id.ts +63 -0
- package/src/crypto/signature.ts +58 -0
- package/src/crypto/token.ts +86 -0
- package/src/crypto/web-provider.ts +32 -0
- package/src/errors.ts +109 -0
- package/src/generated/fault-codes.ts +1338 -0
- package/src/generated/index.ts +4 -0
- package/src/generated/schema.ts +3760 -0
- package/src/generated/types.ts +5829 -0
- package/src/index.ts +22 -0
- package/src/invoice/payload.ts +102 -0
- package/src/money/decimal.ts +218 -0
- package/src/money/summary.ts +380 -0
- package/src/time.ts +44 -0
- package/src/validation/issue.ts +102 -0
- package/src/validation/rules.ts +548 -0
- package/src/validation/schema.ts +315 -0
- package/src/validation/tax-number.ts +96 -0
- package/src/validation/validate.ts +45 -0
- package/src/xml/descriptor.ts +112 -0
- package/src/xml/read.ts +303 -0
- package/src/xml/write.ts +391 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 open-nav contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
This repository vendors XML schemas, sample documents and validation message
|
|
26
|
+
catalogues published by the Hungarian Tax and Customs Administration
|
|
27
|
+
(Nemzeti Adó- és Vámhivatal) under the MIT license. Those files live under
|
|
28
|
+
`schemas/` and `conformance/`, retain NAV's copyright, and are covered by
|
|
29
|
+
`schemas/NAV-LICENCE.md`. See `schemas/README.md` for provenance.
|
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# @open-nav/core
|
|
2
|
+
|
|
3
|
+
The parts of the NAV Online Számla interface that have a right answer:
|
|
4
|
+
authentication, the schema, XML, exact decimal arithmetic and validation.
|
|
5
|
+
No network access — everything here is pure, so it is testable and safe to
|
|
6
|
+
run anywhere.
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npm install @open-nav/core
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
## Authentication
|
|
13
|
+
|
|
14
|
+
Every request NAV accepts carries a SHA-512 password hash and a SHA3-512
|
|
15
|
+
request signature. The signature is not over the request body: it is over
|
|
16
|
+
`requestId + yyyyMMddHHmmss + signKey`, followed by a **concatenation of
|
|
17
|
+
per-invoice hashes in index order** when the operation submits invoices.
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { passwordHash, requestSignature, createRequestId } from '@open-nav/core';
|
|
21
|
+
|
|
22
|
+
const requestId = createRequestId(); // safe against the 30 char / [+a-zA-Z0-9_] rule
|
|
23
|
+
const timestamp = new Date();
|
|
24
|
+
|
|
25
|
+
const signature = requestSignature(requestId, timestamp, process.env.NAV_SIGN_KEY!, [
|
|
26
|
+
{ index: 1, operation: 'CREATE', base64Payload: encoded },
|
|
27
|
+
]);
|
|
28
|
+
const hash = passwordHash(process.env.NAV_PASSWORD!); // the SHA-512 half
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Verified against **all 11 of NAV's published request samples**, the
|
|
32
|
+
three-invoice batch included, which is the case that catches an
|
|
33
|
+
implementation that hashes in the wrong order or hashes a re-serialised copy
|
|
34
|
+
of the payload.
|
|
35
|
+
|
|
36
|
+
`decodeExchangeToken` handles the other half: the token NAV returns is
|
|
37
|
+
AES-128-ECB encrypted with your exchange key, and arrives padded or unpadded
|
|
38
|
+
depending on the operation.
|
|
39
|
+
|
|
40
|
+
## The schema, generated from the XSDs
|
|
41
|
+
|
|
42
|
+
The types and the runtime metadata that drives serialisation, parsing and
|
|
43
|
+
validation are generated from NAV's own XSDs — ~700 elements across six
|
|
44
|
+
namespaces — and must not be hand-edited. One descriptor table drives all
|
|
45
|
+
three directions, so they cannot disagree with each other.
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import {
|
|
49
|
+
serializeDocument,
|
|
50
|
+
parseDocumentAs,
|
|
51
|
+
encodeInvoiceData,
|
|
52
|
+
decodeInvoiceData,
|
|
53
|
+
} from '@open-nav/core';
|
|
54
|
+
|
|
55
|
+
const xml = serializeDocument('InvoiceData', document);
|
|
56
|
+
const back = parseDocumentAs<InvoiceData>(xml, 'InvoiceData');
|
|
57
|
+
const base64 = encodeInvoiceData(document); // what goes in the request
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
All **41 official NAV sample documents** round-trip to canonically equal XML.
|
|
61
|
+
|
|
62
|
+
Namespaces are the trap worth naming: a tax number sits in `OSA/3.0/data`
|
|
63
|
+
while its `taxpayerId` child sits in `OSA/3.0/base`. The generated
|
|
64
|
+
descriptors know which, so you never write a prefix.
|
|
65
|
+
|
|
66
|
+
## Exact arithmetic
|
|
67
|
+
|
|
68
|
+
Invoice amounts run to 18 digits and quantities to 10 decimal places. IEEE
|
|
69
|
+
754 doubles cannot carry either, and being one forint out rejects the whole
|
|
70
|
+
batch, so amounts are **decimal strings end to end** and arithmetic goes
|
|
71
|
+
through `Decimal`, which is BigInt-scaled.
|
|
72
|
+
|
|
73
|
+
```ts
|
|
74
|
+
import { Decimal, computeInvoiceSummary, checkInvoiceSummary } from '@open-nav/core';
|
|
75
|
+
|
|
76
|
+
Decimal.from('0.1').add('0.2').toString(); // '0.3', not 0.30000000000000004
|
|
77
|
+
|
|
78
|
+
const invoice = document.invoiceMain.invoice!; // an InvoiceType
|
|
79
|
+
const summary = computeInvoiceSummary(invoice); // per VAT rate, plus the totals
|
|
80
|
+
const findings = checkInvoiceSummary(invoice); // where it fails to reconcile
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Summation is not the whole rule. A line that omits its VAT amount has it
|
|
84
|
+
**derived from the rate**, a VAT exemption is identified by its legal case
|
|
85
|
+
and not by the free text beside it, and a foreign-currency invoice needs a
|
|
86
|
+
forint twin for every amount. Reproduces the summaries of 24 of NAV's 30
|
|
87
|
+
sample invoices; the remaining 6 are internally contradictory upstream — see
|
|
88
|
+
[`conformance/README.md`](../../conformance/README.md).
|
|
89
|
+
|
|
90
|
+
## Validating before NAV does
|
|
91
|
+
|
|
92
|
+
Two layers, both local: the generated schema, and the business rules that are
|
|
93
|
+
`INVALID_...` faults in NAV's catalogue.
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
import { validateInvoice, faultMessage } from '@open-nav/core';
|
|
97
|
+
|
|
98
|
+
const report = validateInvoice(document, { operation: 'CREATE', language: 'hu' });
|
|
99
|
+
if (!report.valid) {
|
|
100
|
+
for (const issue of report.errors) {
|
|
101
|
+
console.error(issue.path, issue.code, issue.message, issue.navMessage);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
faultMessage('ANNULMENT_IN_PROGRESS', 'hu'); // NAV's own wording, in hu, en or de
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Findings carry NAV's own fault code where one exists, so an error you see
|
|
108
|
+
locally is the error you would have seen remotely. The catalogue is generated
|
|
109
|
+
from `schemas/i18n/` — 236 codes, in Hungarian, English and German. It covers
|
|
110
|
+
NAV's _business validation_ codes; the technical ones (`INVALID_SIGNATURE`,
|
|
111
|
+
`INVALID_REQUEST_ID` and the rest) are not in NAV's message files, and arrive
|
|
112
|
+
with their own text on the response instead.
|
|
113
|
+
|
|
114
|
+
Rules NAV can only decide server-side — whether the invoice number is
|
|
115
|
+
already used, whether the customer's tax number is live — are listed in
|
|
116
|
+
`LOCALLY_UNDECIDABLE` rather than guessed at.
|
|
117
|
+
|
|
118
|
+
Warnings do not make a report invalid, and that distinction is load-bearing:
|
|
119
|
+
the taxpayer-id check digit is a warning because 2 of the 4 tax numbers in
|
|
120
|
+
NAV's own samples fail it while NAV itself validates against its registry. A
|
|
121
|
+
validator that cries wolf gets switched off.
|
|
122
|
+
|
|
123
|
+
## Also here
|
|
124
|
+
|
|
125
|
+
`OPERATIONS`, `BASE_URLS`, `MAX_INVOICE_BATCH_SIZE`, `QUERY_PAGE_SIZE` and
|
|
126
|
+
the other interface constants; `parseTaxNumber` and the county/VAT-code
|
|
127
|
+
tables; NAV's timestamp formats; and the `NavError` hierarchy the client
|
|
128
|
+
throws.
|
|
129
|
+
|
|
130
|
+
## Licence
|
|
131
|
+
|
|
132
|
+
MIT. Not affiliated with NAV.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol level constants for the NAV Online Számla (Online Invoice) system.
|
|
3
|
+
*
|
|
4
|
+
* The reference documentation is published by NAV at
|
|
5
|
+
* https://onlineszamla.nav.gov.hu/dokumentaciok — this library targets
|
|
6
|
+
* interface version 3.0 of the invoice service.
|
|
7
|
+
*/
|
|
8
|
+
/** XML namespace of the invoice service request/response envelopes. */
|
|
9
|
+
export declare const NS_API = "http://schemas.nav.gov.hu/OSA/3.0/api";
|
|
10
|
+
/** XML namespace of the invoice payload (`InvoiceData`). */
|
|
11
|
+
export declare const NS_DATA = "http://schemas.nav.gov.hu/OSA/3.0/data";
|
|
12
|
+
/** XML namespace of shared invoice building blocks (addresses, tax numbers). */
|
|
13
|
+
export declare const NS_BASE = "http://schemas.nav.gov.hu/OSA/3.0/base";
|
|
14
|
+
/** XML namespace of the annulment payload (`InvoiceAnnulment`). */
|
|
15
|
+
export declare const NS_ANNUL = "http://schemas.nav.gov.hu/OSA/3.0/annul";
|
|
16
|
+
/** XML namespace of the NTCA common header/user blocks. */
|
|
17
|
+
export declare const NS_COMMON = "http://schemas.nav.gov.hu/NTCA/1.0/common";
|
|
18
|
+
/** Value of `header/requestVersion` for this interface version. */
|
|
19
|
+
export declare const REQUEST_VERSION = "3.0";
|
|
20
|
+
/** Value of `header/headerVersion` for this interface version. */
|
|
21
|
+
export declare const HEADER_VERSION = "1.0";
|
|
22
|
+
/** Base URL of the NAV production invoice service. */
|
|
23
|
+
export declare const PRODUCTION_BASE_URL = "https://api.onlineszamla.nav.gov.hu/invoiceService/v3";
|
|
24
|
+
/** Base URL of the NAV test ("teszt") invoice service. */
|
|
25
|
+
export declare const TEST_BASE_URL = "https://api-test.onlineszamla.nav.gov.hu/invoiceService/v3";
|
|
26
|
+
/** Well known NAV environments and their base URLs. */
|
|
27
|
+
export declare const BASE_URLS: {
|
|
28
|
+
readonly production: "https://api.onlineszamla.nav.gov.hu/invoiceService/v3";
|
|
29
|
+
readonly test: "https://api-test.onlineszamla.nav.gov.hu/invoiceService/v3";
|
|
30
|
+
};
|
|
31
|
+
export type NavEnvironment = keyof typeof BASE_URLS;
|
|
32
|
+
/** Operation names (path segments) exposed by the invoice service. */
|
|
33
|
+
export declare const OPERATIONS: readonly ["tokenExchange", "manageInvoice", "manageAnnulment", "queryTransactionStatus", "queryTransactionList", "queryInvoiceData", "queryInvoiceDigest", "queryInvoiceChainDigest", "queryInvoiceCheck", "queryTaxpayer"];
|
|
34
|
+
export type NavOperation = (typeof OPERATIONS)[number];
|
|
35
|
+
/**
|
|
36
|
+
* Maximum number of invoice operations NAV accepts in a single
|
|
37
|
+
* `manageInvoice` request.
|
|
38
|
+
*/
|
|
39
|
+
export declare const MAX_INVOICE_BATCH_SIZE = 100;
|
|
40
|
+
/** Page size used by NAV for every paged query response. */
|
|
41
|
+
export declare const QUERY_PAGE_SIZE = 100;
|
|
42
|
+
/**
|
|
43
|
+
* Maximum length of the `requestId` header field. NAV also requires it to be
|
|
44
|
+
* unique per taxpayer, forever.
|
|
45
|
+
*/
|
|
46
|
+
export declare const REQUEST_ID_MAX_LENGTH = 30;
|
|
47
|
+
/** Characters NAV allows in a `requestId`. */
|
|
48
|
+
export declare const REQUEST_ID_PATTERN: RegExp;
|
|
49
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,uEAAuE;AACvE,eAAO,MAAM,MAAM,0CAA0C,CAAC;AAE9D,4DAA4D;AAC5D,eAAO,MAAM,OAAO,2CAA2C,CAAC;AAEhE,gFAAgF;AAChF,eAAO,MAAM,OAAO,2CAA2C,CAAC;AAEhE,mEAAmE;AACnE,eAAO,MAAM,QAAQ,4CAA4C,CAAC;AAElE,2DAA2D;AAC3D,eAAO,MAAM,SAAS,8CAA8C,CAAC;AAErE,mEAAmE;AACnE,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC,kEAAkE;AAClE,eAAO,MAAM,cAAc,QAAQ,CAAC;AAEpC,sDAAsD;AACtD,eAAO,MAAM,mBAAmB,0DAA0D,CAAC;AAE3F,0DAA0D;AAC1D,eAAO,MAAM,aAAa,+DAA+D,CAAC;AAE1F,uDAAuD;AACvD,eAAO,MAAM,SAAS;;;CAGZ,CAAC;AAEX,MAAM,MAAM,cAAc,GAAG,MAAM,OAAO,SAAS,CAAC;AAEpD,sEAAsE;AACtE,eAAO,MAAM,UAAU,6NAWb,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEvD;;;GAGG;AACH,eAAO,MAAM,sBAAsB,MAAM,CAAC;AAE1C,4DAA4D;AAC5D,eAAO,MAAM,eAAe,MAAM,CAAC;AAEnC;;;GAGG;AACH,eAAO,MAAM,qBAAqB,KAAK,CAAC;AAExC,8CAA8C;AAC9C,eAAO,MAAM,kBAAkB,QAA0B,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol level constants for the NAV Online Számla (Online Invoice) system.
|
|
3
|
+
*
|
|
4
|
+
* The reference documentation is published by NAV at
|
|
5
|
+
* https://onlineszamla.nav.gov.hu/dokumentaciok — this library targets
|
|
6
|
+
* interface version 3.0 of the invoice service.
|
|
7
|
+
*/
|
|
8
|
+
/** XML namespace of the invoice service request/response envelopes. */
|
|
9
|
+
export const NS_API = 'http://schemas.nav.gov.hu/OSA/3.0/api';
|
|
10
|
+
/** XML namespace of the invoice payload (`InvoiceData`). */
|
|
11
|
+
export const NS_DATA = 'http://schemas.nav.gov.hu/OSA/3.0/data';
|
|
12
|
+
/** XML namespace of shared invoice building blocks (addresses, tax numbers). */
|
|
13
|
+
export const NS_BASE = 'http://schemas.nav.gov.hu/OSA/3.0/base';
|
|
14
|
+
/** XML namespace of the annulment payload (`InvoiceAnnulment`). */
|
|
15
|
+
export const NS_ANNUL = 'http://schemas.nav.gov.hu/OSA/3.0/annul';
|
|
16
|
+
/** XML namespace of the NTCA common header/user blocks. */
|
|
17
|
+
export const NS_COMMON = 'http://schemas.nav.gov.hu/NTCA/1.0/common';
|
|
18
|
+
/** Value of `header/requestVersion` for this interface version. */
|
|
19
|
+
export const REQUEST_VERSION = '3.0';
|
|
20
|
+
/** Value of `header/headerVersion` for this interface version. */
|
|
21
|
+
export const HEADER_VERSION = '1.0';
|
|
22
|
+
/** Base URL of the NAV production invoice service. */
|
|
23
|
+
export const PRODUCTION_BASE_URL = 'https://api.onlineszamla.nav.gov.hu/invoiceService/v3';
|
|
24
|
+
/** Base URL of the NAV test ("teszt") invoice service. */
|
|
25
|
+
export const TEST_BASE_URL = 'https://api-test.onlineszamla.nav.gov.hu/invoiceService/v3';
|
|
26
|
+
/** Well known NAV environments and their base URLs. */
|
|
27
|
+
export const BASE_URLS = {
|
|
28
|
+
production: PRODUCTION_BASE_URL,
|
|
29
|
+
test: TEST_BASE_URL,
|
|
30
|
+
};
|
|
31
|
+
/** Operation names (path segments) exposed by the invoice service. */
|
|
32
|
+
export const OPERATIONS = [
|
|
33
|
+
'tokenExchange',
|
|
34
|
+
'manageInvoice',
|
|
35
|
+
'manageAnnulment',
|
|
36
|
+
'queryTransactionStatus',
|
|
37
|
+
'queryTransactionList',
|
|
38
|
+
'queryInvoiceData',
|
|
39
|
+
'queryInvoiceDigest',
|
|
40
|
+
'queryInvoiceChainDigest',
|
|
41
|
+
'queryInvoiceCheck',
|
|
42
|
+
'queryTaxpayer',
|
|
43
|
+
];
|
|
44
|
+
/**
|
|
45
|
+
* Maximum number of invoice operations NAV accepts in a single
|
|
46
|
+
* `manageInvoice` request.
|
|
47
|
+
*/
|
|
48
|
+
export const MAX_INVOICE_BATCH_SIZE = 100;
|
|
49
|
+
/** Page size used by NAV for every paged query response. */
|
|
50
|
+
export const QUERY_PAGE_SIZE = 100;
|
|
51
|
+
/**
|
|
52
|
+
* Maximum length of the `requestId` header field. NAV also requires it to be
|
|
53
|
+
* unique per taxpayer, forever.
|
|
54
|
+
*/
|
|
55
|
+
export const REQUEST_ID_MAX_LENGTH = 30;
|
|
56
|
+
/** Characters NAV allows in a `requestId`. */
|
|
57
|
+
export const REQUEST_ID_PATTERN = /^[+a-zA-Z0-9_]{1,30}$/;
|
|
58
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.js","sourceRoot":"","sources":["../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,uEAAuE;AACvE,MAAM,CAAC,MAAM,MAAM,GAAG,uCAAuC,CAAC;AAE9D,4DAA4D;AAC5D,MAAM,CAAC,MAAM,OAAO,GAAG,wCAAwC,CAAC;AAEhE,gFAAgF;AAChF,MAAM,CAAC,MAAM,OAAO,GAAG,wCAAwC,CAAC;AAEhE,mEAAmE;AACnE,MAAM,CAAC,MAAM,QAAQ,GAAG,yCAAyC,CAAC;AAElE,2DAA2D;AAC3D,MAAM,CAAC,MAAM,SAAS,GAAG,2CAA2C,CAAC;AAErE,mEAAmE;AACnE,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AAErC,kEAAkE;AAClE,MAAM,CAAC,MAAM,cAAc,GAAG,KAAK,CAAC;AAEpC,sDAAsD;AACtD,MAAM,CAAC,MAAM,mBAAmB,GAAG,uDAAuD,CAAC;AAE3F,0DAA0D;AAC1D,MAAM,CAAC,MAAM,aAAa,GAAG,4DAA4D,CAAC;AAE1F,uDAAuD;AACvD,MAAM,CAAC,MAAM,SAAS,GAAG;IACvB,UAAU,EAAE,mBAAmB;IAC/B,IAAI,EAAE,aAAa;CACX,CAAC;AAIX,sEAAsE;AACtE,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,eAAe;IACf,eAAe;IACf,iBAAiB;IACjB,wBAAwB;IACxB,sBAAsB;IACtB,kBAAkB;IAClB,oBAAoB;IACpB,yBAAyB;IACzB,mBAAmB;IACnB,eAAe;CACP,CAAC;AAIX;;;GAGG;AACH,MAAM,CAAC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAE1C,4DAA4D;AAC5D,MAAM,CAAC,MAAM,eAAe,GAAG,GAAG,CAAC;AAEnC;;;GAGG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAExC,8CAA8C;AAC9C,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** Uppercase hexadecimal SHA-512 digest of a UTF-8 string. */
|
|
2
|
+
export declare function sha512(value: string): string;
|
|
3
|
+
/** Uppercase hexadecimal SHA3-512 digest of a UTF-8 string. */
|
|
4
|
+
export declare function sha3_512(value: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Hash of the technical user's password for the `user/passwordHash` field.
|
|
7
|
+
*
|
|
8
|
+
* NAV expects the uppercase hex SHA-512 digest of the password, declared on
|
|
9
|
+
* the element with `cryptoType="SHA-512"`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function passwordHash(password: string): string;
|
|
12
|
+
/** `cryptoType` attribute value used for `passwordHash`. */
|
|
13
|
+
export declare const PASSWORD_HASH_CRYPTO_TYPE = "SHA-512";
|
|
14
|
+
/** `cryptoType` attribute value used for `requestSignature` and invoice hashes. */
|
|
15
|
+
export declare const SIGNATURE_CRYPTO_TYPE = "SHA3-512";
|
|
16
|
+
//# sourceMappingURL=hash.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/crypto/hash.ts"],"names":[],"mappings":"AAUA,8DAA8D;AAC9D,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE5C;AAED,+DAA+D;AAC/D,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAE9C;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,4DAA4D;AAC5D,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAEnD,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,aAAa,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { getCryptoProvider } from './provider.js';
|
|
2
|
+
const encoder = new TextEncoder();
|
|
3
|
+
function toHexUpper(bytes) {
|
|
4
|
+
let out = '';
|
|
5
|
+
for (const byte of bytes)
|
|
6
|
+
out += byte.toString(16).padStart(2, '0');
|
|
7
|
+
return out.toUpperCase();
|
|
8
|
+
}
|
|
9
|
+
/** Uppercase hexadecimal SHA-512 digest of a UTF-8 string. */
|
|
10
|
+
export function sha512(value) {
|
|
11
|
+
return toHexUpper(getCryptoProvider().sha512(encoder.encode(value)));
|
|
12
|
+
}
|
|
13
|
+
/** Uppercase hexadecimal SHA3-512 digest of a UTF-8 string. */
|
|
14
|
+
export function sha3_512(value) {
|
|
15
|
+
return toHexUpper(getCryptoProvider().sha3_512(encoder.encode(value)));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Hash of the technical user's password for the `user/passwordHash` field.
|
|
19
|
+
*
|
|
20
|
+
* NAV expects the uppercase hex SHA-512 digest of the password, declared on
|
|
21
|
+
* the element with `cryptoType="SHA-512"`.
|
|
22
|
+
*/
|
|
23
|
+
export function passwordHash(password) {
|
|
24
|
+
return sha512(password);
|
|
25
|
+
}
|
|
26
|
+
/** `cryptoType` attribute value used for `passwordHash`. */
|
|
27
|
+
export const PASSWORD_HASH_CRYPTO_TYPE = 'SHA-512';
|
|
28
|
+
/** `cryptoType` attribute value used for `requestSignature` and invoice hashes. */
|
|
29
|
+
export const SIGNATURE_CRYPTO_TYPE = 'SHA3-512';
|
|
30
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/crypto/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;AAElC,SAAS,UAAU,CAAC,KAAiB;IACnC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,GAAG,IAAI,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpE,OAAO,GAAG,CAAC,WAAW,EAAE,CAAC;AAC3B,CAAC;AAED,8DAA8D;AAC9D,MAAM,UAAU,MAAM,CAAC,KAAa;IAClC,OAAO,UAAU,CAAC,iBAAiB,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,QAAQ,CAAC,KAAa;IACpC,OAAO,UAAU,CAAC,iBAAiB,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AACzE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,QAAgB;IAC3C,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1B,CAAC;AAED,4DAA4D;AAC5D,MAAM,CAAC,MAAM,yBAAyB,GAAG,SAAS,CAAC;AAEnD,mFAAmF;AACnF,MAAM,CAAC,MAAM,qBAAqB,GAAG,UAAU,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CryptoProvider } from './provider.js';
|
|
2
|
+
/**
|
|
3
|
+
* The default {@link CryptoProvider}, backed by Node's built-in `node:crypto`.
|
|
4
|
+
*
|
|
5
|
+
* Works on Node, Bun and Deno. It does **not** work on Cloudflare Workers,
|
|
6
|
+
* whose `node:crypto` lacks SHA-3 — use {@link createWebCryptoProvider} there.
|
|
7
|
+
*/
|
|
8
|
+
export declare const nodeCryptoProvider: CryptoProvider;
|
|
9
|
+
//# sourceMappingURL=node-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-provider.d.ts","sourceRoot":"","sources":["../../src/crypto/node-provider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAShC,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createHash, createDecipheriv, randomBytes } from 'node:crypto';
|
|
2
|
+
/**
|
|
3
|
+
* The default {@link CryptoProvider}, backed by Node's built-in `node:crypto`.
|
|
4
|
+
*
|
|
5
|
+
* Works on Node, Bun and Deno. It does **not** work on Cloudflare Workers,
|
|
6
|
+
* whose `node:crypto` lacks SHA-3 — use {@link createWebCryptoProvider} there.
|
|
7
|
+
*/
|
|
8
|
+
export const nodeCryptoProvider = {
|
|
9
|
+
sha512: (data) => Uint8Array.from(createHash('sha512').update(data).digest()),
|
|
10
|
+
sha3_512: (data) => Uint8Array.from(createHash('sha3-512').update(data).digest()),
|
|
11
|
+
aes128EcbDecrypt: (ciphertext, key) => {
|
|
12
|
+
const decipher = createDecipheriv('aes-128-ecb', key, null);
|
|
13
|
+
decipher.setAutoPadding(false);
|
|
14
|
+
return Uint8Array.from(Buffer.concat([decipher.update(ciphertext), decipher.final()]));
|
|
15
|
+
},
|
|
16
|
+
randomBytes: (length) => Uint8Array.from(randomBytes(length)),
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=node-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"node-provider.js","sourceRoot":"","sources":["../../src/crypto/node-provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGxE;;;;;GAKG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAmB;IAChD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;IAC7E,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC;IACjF,gBAAgB,EAAE,CAAC,UAAU,EAAE,GAAG,EAAE,EAAE;QACpC,MAAM,QAAQ,GAAG,gBAAgB,CAAC,aAAa,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QAC5D,QAAQ,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC;QAC/B,OAAO,UAAU,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACzF,CAAC;IACD,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;CAC9D,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The low-level cryptographic primitives the NAV protocol needs, abstracted so
|
|
3
|
+
* the library can run on any JavaScript runtime.
|
|
4
|
+
*
|
|
5
|
+
* The default is {@link nodeCryptoProvider}, backed by Node's built-in
|
|
6
|
+
* `node:crypto`. On runtimes whose `node:crypto` lacks SHA-3 — notably
|
|
7
|
+
* Cloudflare Workers (workerd/BoringSSL), where `createHash('sha3-512')` throws
|
|
8
|
+
* `Digest method not supported` — install {@link createWebCryptoProvider} via
|
|
9
|
+
* {@link setCryptoProvider} once at startup, before making any request.
|
|
10
|
+
*/
|
|
11
|
+
export interface CryptoProvider {
|
|
12
|
+
/** Raw SHA-512 digest of `data`. */
|
|
13
|
+
sha512(data: Uint8Array): Uint8Array;
|
|
14
|
+
/** Raw SHA3-512 digest of `data`. */
|
|
15
|
+
sha3_512(data: Uint8Array): Uint8Array;
|
|
16
|
+
/** AES-128 ECB decryption without padding; `key` is exactly 16 bytes. */
|
|
17
|
+
aes128EcbDecrypt(ciphertext: Uint8Array, key: Uint8Array): Uint8Array;
|
|
18
|
+
/** `length` cryptographically strong random bytes. */
|
|
19
|
+
randomBytes(length: number): Uint8Array;
|
|
20
|
+
}
|
|
21
|
+
/** The crypto provider currently backing every cryptographic helper. */
|
|
22
|
+
export declare function getCryptoProvider(): CryptoProvider;
|
|
23
|
+
/**
|
|
24
|
+
* Replace the active crypto provider. Call once at startup, before any NAV
|
|
25
|
+
* request — e.g. `setCryptoProvider(createWebCryptoProvider())` in a Worker.
|
|
26
|
+
*/
|
|
27
|
+
export declare function setCryptoProvider(provider: CryptoProvider): void;
|
|
28
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../src/crypto/provider.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,WAAW,cAAc;IAC7B,oCAAoC;IACpC,MAAM,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC;IACrC,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,UAAU,CAAC;IACvC,yEAAyE;IACzE,gBAAgB,CAAC,UAAU,EAAE,UAAU,EAAE,GAAG,EAAE,UAAU,GAAG,UAAU,CAAC;IACtE,sDAAsD;IACtD,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAAC;CACzC;AAID,wEAAwE;AACxE,wBAAgB,iBAAiB,IAAI,cAAc,CAElD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAEhE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { nodeCryptoProvider } from './node-provider.js';
|
|
2
|
+
let activeProvider = nodeCryptoProvider;
|
|
3
|
+
/** The crypto provider currently backing every cryptographic helper. */
|
|
4
|
+
export function getCryptoProvider() {
|
|
5
|
+
return activeProvider;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Replace the active crypto provider. Call once at startup, before any NAV
|
|
9
|
+
* request — e.g. `setCryptoProvider(createWebCryptoProvider())` in a Worker.
|
|
10
|
+
*/
|
|
11
|
+
export function setCryptoProvider(provider) {
|
|
12
|
+
activeProvider = provider;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../src/crypto/provider.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAuBxD,IAAI,cAAc,GAAmB,kBAAkB,CAAC;AAExD,wEAAwE;AACxE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,cAAc,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAwB;IACxD,cAAc,GAAG,QAAQ,CAAC;AAC5B,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a `requestId` that satisfies NAV's constraints.
|
|
3
|
+
*
|
|
4
|
+
* NAV requires the value to match `[+a-zA-Z0-9_]{1,30}` and to be unique for
|
|
5
|
+
* the taxpayer across all requests ever sent — a replayed identifier is
|
|
6
|
+
* rejected with `INVALID_REQUEST_ID`. The generated value combines a
|
|
7
|
+
* millisecond timestamp with random characters, which is collision safe for
|
|
8
|
+
* practical throughput, but persisting issued identifiers is still the only
|
|
9
|
+
* way to be certain after a clock change.
|
|
10
|
+
*
|
|
11
|
+
* @param prefix optional caller prefix, useful for tracing (`[a-zA-Z0-9_+]`)
|
|
12
|
+
*/
|
|
13
|
+
export declare function createRequestId(prefix?: string): string;
|
|
14
|
+
/** Throw unless `value` is a valid `requestId`. */
|
|
15
|
+
export declare function assertRequestId(value: string): void;
|
|
16
|
+
//# sourceMappingURL=request-id.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-id.d.ts","sourceRoot":"","sources":["../../src/crypto/request-id.ts"],"names":[],"mappings":"AAMA;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,MAAM,SAAO,GAAG,MAAM,CAcrD;AAED,mDAAmD;AACnD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAUnD"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { getCryptoProvider } from './provider.js';
|
|
2
|
+
import { REQUEST_ID_MAX_LENGTH, REQUEST_ID_PATTERN } from '../constants.js';
|
|
3
|
+
import { NavValidationError } from '../errors.js';
|
|
4
|
+
const ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
5
|
+
/**
|
|
6
|
+
* Generate a `requestId` that satisfies NAV's constraints.
|
|
7
|
+
*
|
|
8
|
+
* NAV requires the value to match `[+a-zA-Z0-9_]{1,30}` and to be unique for
|
|
9
|
+
* the taxpayer across all requests ever sent — a replayed identifier is
|
|
10
|
+
* rejected with `INVALID_REQUEST_ID`. The generated value combines a
|
|
11
|
+
* millisecond timestamp with random characters, which is collision safe for
|
|
12
|
+
* practical throughput, but persisting issued identifiers is still the only
|
|
13
|
+
* way to be certain after a clock change.
|
|
14
|
+
*
|
|
15
|
+
* @param prefix optional caller prefix, useful for tracing (`[a-zA-Z0-9_+]`)
|
|
16
|
+
*/
|
|
17
|
+
export function createRequestId(prefix = 'ON') {
|
|
18
|
+
assertRequestIdChars(prefix, 'prefix');
|
|
19
|
+
const stamp = Date.now().toString(36).toUpperCase();
|
|
20
|
+
const available = REQUEST_ID_MAX_LENGTH - prefix.length - stamp.length;
|
|
21
|
+
if (available < 4) {
|
|
22
|
+
throw new NavValidationError('requestId prefix is too long', [
|
|
23
|
+
{
|
|
24
|
+
path: 'prefix',
|
|
25
|
+
code: 'REQUEST_ID_PREFIX_TOO_LONG',
|
|
26
|
+
message: `leaves only ${available} characters of entropy, need at least 4`,
|
|
27
|
+
},
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
return `${prefix}${stamp}${randomChars(Math.min(available, 8))}`;
|
|
31
|
+
}
|
|
32
|
+
/** Throw unless `value` is a valid `requestId`. */
|
|
33
|
+
export function assertRequestId(value) {
|
|
34
|
+
if (!REQUEST_ID_PATTERN.test(value)) {
|
|
35
|
+
throw new NavValidationError('Invalid requestId', [
|
|
36
|
+
{
|
|
37
|
+
path: 'requestId',
|
|
38
|
+
code: 'INVALID_REQUEST_ID',
|
|
39
|
+
message: `must match ${REQUEST_ID_PATTERN.source}, got ${JSON.stringify(value)}`,
|
|
40
|
+
},
|
|
41
|
+
]);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function assertRequestIdChars(value, path) {
|
|
45
|
+
if (!/^[+a-zA-Z0-9_]*$/.test(value)) {
|
|
46
|
+
throw new NavValidationError('Invalid requestId characters', [
|
|
47
|
+
{ path, code: 'INVALID_REQUEST_ID', message: 'may only contain [+a-zA-Z0-9_]' },
|
|
48
|
+
]);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function randomChars(length) {
|
|
52
|
+
const bytes = getCryptoProvider().randomBytes(length);
|
|
53
|
+
let out = '';
|
|
54
|
+
for (let i = 0; i < length; i += 1) {
|
|
55
|
+
out += ALPHABET[bytes[i] % ALPHABET.length];
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=request-id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request-id.js","sourceRoot":"","sources":["../../src/crypto/request-id.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,QAAQ,GAAG,gEAAgE,CAAC;AAElF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,MAAM,GAAG,IAAI;IAC3C,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACvC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;IACpD,MAAM,SAAS,GAAG,qBAAqB,GAAG,MAAM,CAAC,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IACvE,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,MAAM,IAAI,kBAAkB,CAAC,8BAA8B,EAAE;YAC3D;gBACE,IAAI,EAAE,QAAQ;gBACd,IAAI,EAAE,4BAA4B;gBAClC,OAAO,EAAE,eAAe,SAAS,yCAAyC;aAC3E;SACF,CAAC,CAAC;IACL,CAAC;IACD,OAAO,GAAG,MAAM,GAAG,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACnE,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,kBAAkB,CAAC,mBAAmB,EAAE;YAChD;gBACE,IAAI,EAAE,WAAW;gBACjB,IAAI,EAAE,oBAAoB;gBAC1B,OAAO,EAAE,cAAc,kBAAkB,CAAC,MAAM,SAAS,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE;aACjF;SACF,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,SAAS,oBAAoB,CAAC,KAAa,EAAE,IAAY;IACvD,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,kBAAkB,CAAC,8BAA8B,EAAE;YAC3D,EAAE,IAAI,EAAE,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,gCAAgC,EAAE;SAChF,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,SAAS,WAAW,CAAC,MAAc;IACjC,MAAM,KAAK,GAAG,iBAAiB,EAAE,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;IACtD,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,GAAG,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single invoice (or annulment) operation as it contributes to the request
|
|
3
|
+
* signature of a `manageInvoice` / `manageAnnulment` call.
|
|
4
|
+
*/
|
|
5
|
+
export interface SignedOperation {
|
|
6
|
+
/** 1-based position inside the batch; operations are hashed in index order. */
|
|
7
|
+
index: number;
|
|
8
|
+
/** `CREATE`, `MODIFY`, `STORNO` or `ANNUL`. */
|
|
9
|
+
operation: string;
|
|
10
|
+
/** Base64 encoded payload exactly as it appears in the request XML. */
|
|
11
|
+
base64Payload: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Hash of one invoice operation, as consumed by {@link requestSignature} and
|
|
15
|
+
* as sent in the optional `electronicInvoiceHash` element.
|
|
16
|
+
*
|
|
17
|
+
* NAV defines it as `SHA3-512(operation || base64Payload)`, uppercase hex.
|
|
18
|
+
*/
|
|
19
|
+
export declare function operationHash(operation: string, base64Payload: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* Compute the `user/requestSignature` value.
|
|
22
|
+
*
|
|
23
|
+
* NAV specifies the signature as
|
|
24
|
+
*
|
|
25
|
+
* ```text
|
|
26
|
+
* SHA3-512( requestId || yyyyMMddHHmmss(timestamp) || signKey || hash(op1) || hash(op2) ... )
|
|
27
|
+
* ```
|
|
28
|
+
*
|
|
29
|
+
* where the per-operation hashes are only present for `manageInvoice` and
|
|
30
|
+
* `manageAnnulment` requests and must be concatenated in ascending `index`
|
|
31
|
+
* order. The timestamp must be the request header timestamp truncated to
|
|
32
|
+
* whole seconds and rendered in UTC without separators.
|
|
33
|
+
*
|
|
34
|
+
* @param requestId the `header/requestId` of the same request
|
|
35
|
+
* @param timestamp the `header/timestamp` of the same request
|
|
36
|
+
* @param signKey the technical user's signature key (aláírókulcs)
|
|
37
|
+
* @param operations invoice/annulment operations of the batch, if any
|
|
38
|
+
*/
|
|
39
|
+
export declare function requestSignature(requestId: string, timestamp: Date | string, signKey: string, operations?: SignedOperation[]): string;
|
|
40
|
+
//# sourceMappingURL=signature.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.d.ts","sourceRoot":"","sources":["../../src/crypto/signature.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,uEAAuE;IACvE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,MAAM,CAE9E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,IAAI,GAAG,MAAM,EACxB,OAAO,EAAE,MAAM,EACf,UAAU,GAAE,eAAe,EAAO,GACjC,MAAM,CAOR"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { sha3_512 } from './hash.js';
|
|
2
|
+
import { toSignatureTimestamp } from '../time.js';
|
|
3
|
+
/**
|
|
4
|
+
* Hash of one invoice operation, as consumed by {@link requestSignature} and
|
|
5
|
+
* as sent in the optional `electronicInvoiceHash` element.
|
|
6
|
+
*
|
|
7
|
+
* NAV defines it as `SHA3-512(operation || base64Payload)`, uppercase hex.
|
|
8
|
+
*/
|
|
9
|
+
export function operationHash(operation, base64Payload) {
|
|
10
|
+
return sha3_512(`${operation}${base64Payload}`);
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Compute the `user/requestSignature` value.
|
|
14
|
+
*
|
|
15
|
+
* NAV specifies the signature as
|
|
16
|
+
*
|
|
17
|
+
* ```text
|
|
18
|
+
* SHA3-512( requestId || yyyyMMddHHmmss(timestamp) || signKey || hash(op1) || hash(op2) ... )
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* where the per-operation hashes are only present for `manageInvoice` and
|
|
22
|
+
* `manageAnnulment` requests and must be concatenated in ascending `index`
|
|
23
|
+
* order. The timestamp must be the request header timestamp truncated to
|
|
24
|
+
* whole seconds and rendered in UTC without separators.
|
|
25
|
+
*
|
|
26
|
+
* @param requestId the `header/requestId` of the same request
|
|
27
|
+
* @param timestamp the `header/timestamp` of the same request
|
|
28
|
+
* @param signKey the technical user's signature key (aláírókulcs)
|
|
29
|
+
* @param operations invoice/annulment operations of the batch, if any
|
|
30
|
+
*/
|
|
31
|
+
export function requestSignature(requestId, timestamp, signKey, operations = []) {
|
|
32
|
+
const operationDigests = [...operations]
|
|
33
|
+
.sort((a, b) => a.index - b.index)
|
|
34
|
+
.map((op) => operationHash(op.operation, op.base64Payload))
|
|
35
|
+
.join('');
|
|
36
|
+
return sha3_512(`${requestId}${toSignatureTimestamp(timestamp)}${signKey}${operationDigests}`);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=signature.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signature.js","sourceRoot":"","sources":["../../src/crypto/signature.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAelD;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,SAAiB,EAAE,aAAqB;IACpE,OAAO,QAAQ,CAAC,GAAG,SAAS,GAAG,aAAa,EAAE,CAAC,CAAC;AAClD,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,gBAAgB,CAC9B,SAAiB,EACjB,SAAwB,EACxB,OAAe,EACf,aAAgC,EAAE;IAElC,MAAM,gBAAgB,GAAG,CAAC,GAAG,UAAU,CAAC;SACrC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC;SACjC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC;SAC1D,IAAI,CAAC,EAAE,CAAC,CAAC;IAEZ,OAAO,QAAQ,CAAC,GAAG,SAAS,GAAG,oBAAoB,CAAC,SAAS,CAAC,GAAG,OAAO,GAAG,gBAAgB,EAAE,CAAC,CAAC;AACjG,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decrypt the `encodedExchangeToken` returned by `tokenExchange`.
|
|
3
|
+
*
|
|
4
|
+
* NAV encrypts the 16 character exchange token with AES-128 in ECB mode using
|
|
5
|
+
* the technical user's exchange key (csereklucs / "XML aláírás kulcs" pair) as
|
|
6
|
+
* the raw key, then base64 encodes the result. Padding is optional in
|
|
7
|
+
* practice: NAV historically returned an unpadded 16 byte block, while some
|
|
8
|
+
* environments return a PKCS#7 padded 32 byte block, so both are accepted.
|
|
9
|
+
*
|
|
10
|
+
* @param encodedToken base64 payload from the response
|
|
11
|
+
* @param exchangeKey 16 character exchange key of the technical user
|
|
12
|
+
*/
|
|
13
|
+
export declare function decodeExchangeToken(encodedToken: string, exchangeKey: string): string;
|
|
14
|
+
//# sourceMappingURL=token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/crypto/token.ts"],"names":[],"mappings":"AA0BA;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,MAAM,CA6BrF"}
|