@polyjose/node 0.1.1 → 0.2.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 CHANGED
@@ -11,146 +11,89 @@ accepted (RFC 8725).
11
11
  npm install @polyjose/node
12
12
  ```
13
13
 
14
- npm also installs the matching prebuilt addon, `@polyjose/node-<platform>`, for macOS (x64,
15
- arm64), Linux glibc and musl (x64, arm64) and Windows (x64, arm64). Each is an optional
16
- dependency at exactly this package's version, and npm installs only the one for your platform.
17
- If you install with `--omit=optional`, no addon is installed and the import throws.
18
-
19
- ### Linux glibc floor
20
-
21
- The Linux glibc addons (`linux-x64-gnu`, `linux-arm64-gnu`) need **glibc 2.28 or later**. That is
22
- also the floor of the official Node.js 22 and later Linux builds, so any glibc system that runs
23
- official Node 22 can load the addon. They are built in the `manylinux_2_28` images (glibc 2.28),
24
- and the build fails if an addon needs a newer glibc symbol version.
25
-
26
- | Distribution | glibc |
27
- |---|---|
28
- | RHEL, AlmaLinux, Rocky Linux 8 | 2.28 |
29
- | Debian 11 | 2.31 |
30
- | Ubuntu 20.04 | 2.31 |
31
- | RHEL, AlmaLinux, Rocky Linux 9 | 2.34 |
32
- | Amazon Linux 2023, AWS Lambda `nodejs22.x` and `nodejs24.x` | 2.34 |
33
- | Ubuntu 22.04 | 2.35 |
34
- | Debian 12 | 2.36 |
35
- | Ubuntu 24.04 | 2.39 |
36
-
37
- Any later release of these works too. Amazon Linux 2 (glibc 2.26) is not supported: it reached its
38
- end of life on 2026-06-30, and official Node 22 doesn't run on it either. Alpine and other musl
39
- systems use the `linux-x64-musl` and `linux-arm64-musl` addons instead.
40
-
41
- ## Module format
42
-
43
- - The package is **ESM only** and needs **Node.js 22.12 or later**.
44
- - CommonJS code can still load it with `require("@polyjose/node")`, through Node's `require(esm)`
45
- (unflagged since 22.12).
46
- - Every operation returns a Promise. The work runs on the libuv thread pool, so it doesn't block
47
- the event loop.
48
- - The API and its TypeScript types are the same as `@polyjose/wasm`, so you can switch between
49
- the two packages by changing the import.
14
+ Prebuilt for macOS, Linux (glibc and musl) and Windows, see the docs for
15
+ [platform support](https://polyjose.dev/reference/node/). Needs Node.js 22.12 or later.
16
+
17
+ ## Import
18
+
19
+ The package is ESM:
20
+
21
+ ```js
22
+ import { jwtSign, jwtVerify } from "@polyjose/node";
23
+ ```
24
+
25
+ CommonJS code can load it too, through Node's `require(esm)`:
26
+
27
+ ```js
28
+ const { jwtSign, jwtVerify } = require("@polyjose/node");
29
+ ```
30
+
31
+ Every function returns a Promise, and the work runs on the libuv thread pool, so it doesn't block
32
+ the event loop. The API is the same as `@polyjose/wasm`'s.
33
+
34
+ ## Quick start
35
+
36
+ Sign a JWT, then verify it with an explicit algorithm allow-list and the issuer and audience you
37
+ expect. `exp` is required and checked, with 60 seconds of leeway (RFC 8725 §3.8 to §3.10).
38
+
39
+ ```js
40
+ import { jwkGenerateForAlg, jwkToPublic, jwtSign, jwtVerify, PolyjoseError } from "@polyjose/node";
41
+
42
+ // An ES256 key pair. The verifier needs only the public half.
43
+ const privateKey = await jwkGenerateForAlg("ES256");
44
+ const publicKey = await jwkToPublic(privateKey);
45
+
46
+ const now = Math.floor(Date.now() / 1000);
47
+ const token = await jwtSign(
48
+ { iss: "https://issuer.example", sub: "user-123", aud: "https://api.example", exp: now + 300 },
49
+ privateKey,
50
+ { alg: "ES256" },
51
+ );
52
+
53
+ try {
54
+ const verified = await jwtVerify(token, publicKey, {
55
+ allowedAlgs: ["ES256"], // never trust the token's own `alg` (RFC 8725 §3.1)
56
+ issuers: ["https://issuer.example"],
57
+ audiences: ["https://api.example"],
58
+ });
59
+ console.log(verified.claims.sub); // "user-123"
60
+ } catch (err) {
61
+ if (err instanceof PolyjoseError) console.error(err.code); // e.g. "TokenExpired"
62
+ else throw err;
63
+ }
64
+ ```
65
+
66
+ Encrypt to a recipient's public key, then decrypt, naming the algorithms you accept:
67
+
68
+ ```js
69
+ import { jweDecryptCompact, jweEncryptCompact, jwkGenerateEc, jwkToPublic } from "@polyjose/node";
70
+
71
+ const recipientKey = await jwkGenerateEc("P-256");
72
+ const recipientPublicKey = await jwkToPublic(recipientKey);
73
+
74
+ const jwe = await jweEncryptCompact(new TextEncoder().encode("top secret"), {
75
+ enc: "A256GCM",
76
+ recipients: [{ alg: "ECDH-ES+A256KW", key: recipientPublicKey }],
77
+ });
78
+
79
+ const decrypted = await jweDecryptCompact(jwe, recipientKey, {
80
+ allowedAlgs: ["ECDH-ES+A256KW"],
81
+ allowedEnc: ["A256GCM"],
82
+ });
83
+ console.log(new TextDecoder().decode(decrypted.plaintext)); // "top secret"
84
+ decrypted.plaintext.fill(0); // results are plain data: wipe secret bytes, and never log them
85
+ ```
50
86
 
51
87
  ## Documentation
52
88
 
53
- Guides and the API reference are at <https://polyjose.dev>. The Node.js guide is at
54
- <https://polyjose.dev/start/node/>.
55
-
56
- ## Loading the addon
57
-
58
- The package loads **only the polyjose addon whose SHA-256 it was built with**. Otherwise the
59
- import throws an `Error` with `code` `ERR_POLYJOSE_ADDON_REFUSED`, and nothing it refused has run.
60
-
61
- - **The hash pin.** The package ships `addon-sha256.json`: the SHA-256 of each platform's addon,
62
- generated when the package is built. Before anything loads, the package picks the one addon file
63
- for your platform, the same one the generated napi-rs loader (`binding.cjs`) would pick:
64
- 1. the file named by `NAPI_RS_NATIVE_LIBRARY_PATH`, if set (see below);
65
- 2. else a local `polyjose.<platform>.node` next to the loader;
66
- 3. else the platform package `@polyjose/node-<platform>`, found through Node's normal module
67
- resolution: this package's `node_modules`, then every ancestor `node_modules`, then
68
- `NODE_PATH`.
69
-
70
- It hashes that file and refuses it unless the hash is the pinned one. The refusal names the
71
- platform, both hashes and where the file came from. There is no fallback to another file. So a
72
- substitute is refused wherever it was planted, a native addon as much as a JavaScript file,
73
- including when the genuine platform package is missing: after `npm install --omit=optional`, or
74
- on a platform polyjose doesn't publish. A package without the map, or without an entry for your
75
- platform, loads nothing.
76
- - **No swap after the check.** The package writes the bytes it checked to a new private directory
77
- in `os.tmpdir()` (mode `0700`; on Windows, your own `%TEMP%`), checks the copy again, and has
78
- the loader load exactly that copy. Then it deletes the copy; the loaded addon stays in memory.
79
- Windows can't delete a loaded DLL, so there each copy (the size of the addon) stays in the
80
- temporary directory. The temporary directory must allow loading native code: if yours is
81
- mounted `noexec`, point `TMPDIR` (`TEMP` on Windows) at one that does.
82
- - **Every candidate is native.** Before that, everything the loader could pick must be a native
83
- `.node` file. If any candidate resolves to something else, such as a JavaScript
84
- `@polyjose/node-<platform>` in a parent folder or on `NODE_PATH`, the import throws before that
85
- file runs.
86
- - **After loading,** the result must be exactly the checked copy, not wrapped by any JavaScript,
87
- and its own `version()` must be this package's version. An addon from another polyjose release
88
- is refused, whatever `NAPI_RS_ENFORCE_VERSION_CHECK` says.
89
- - polyjose **never publishes a `wasm32-wasi` package**. The napi-rs loader still has a WASI
90
- fallback, tried when the native load fails: `./polyjose.wasi.cjs`, then
91
- `@polyjose/node-wasm32-wasi`. The package never reaches it. If either WASI file or package can be
92
- found from the loader's directory, the import throws before anything is loaded. If loading the
93
- checked copy fails, the import throws "Cannot find native binding". For WebAssembly, use
94
- `@polyjose/wasm`.
95
-
96
- These checks keep the package from *choosing* a substitute. They can't protect against code that
97
- already runs in your process, which can change anything.
98
-
99
- ### Environment variables
100
-
101
- | Variable | Effect |
102
- |---|---|
103
- | `NAPI_RS_NATIVE_LIBRARY_PATH` | Loads the addon from this path instead of the installed one, **only if its SHA-256 is the pinned one** for your platform (see below). |
104
- | `NAPI_RS_FORCE_WASI` | **Refused.** If it is set, whatever the value (even `false` or empty), the import throws. |
105
- | `NAPI_RS_WASI_FLAVOR` | **Refused**, like `NAPI_RS_FORCE_WASI`. |
106
- | `NAPI_RS_ENFORCE_VERSION_CHECK` | Read by the generated loader only. The package checks the hash and the version anyway (see above). |
107
-
108
- **`NAPI_RS_NATIVE_LIBRARY_PATH` is held to the same pin.** It names the file the package checks, so
109
- it can only relocate polyjose's own addon, for example out of an archive or a bundle. A JavaScript
110
- file there is refused before it runs; any other native library is refused by its hash. The package
111
- sets the variable to its private copy while the loader runs and restores your value right after.
112
-
113
- ### A panic in the addon aborts the process
114
-
115
- If the Rust code ever panics, Node.js **aborts the whole process** (SIGABRT). The Promise isn't
116
- rejected, and no `catch` or `process.on("uncaughtException")` handler runs. This fails closed:
117
- no partial result is returned, and no memory is corrupted. Today we know of no input that causes a
118
- panic; the core forbids `panic!`, `unwrap` and `expect`. A server that must survive a panic should
119
- run polyjose in a worker process it can restart. Turning panics into rejected Promises is planned.
120
-
121
- ### Side effects of loading
122
-
123
- - **Linux:** to tell musl from glibc, the package reads `/usr/bin/ldd`, as the generated loader
124
- would. If that fails, it calls `process.report.getReport()`, and before that it sets
125
- `process.report.excludeNetwork = true` for the whole process. If there is no report either, the
126
- import throws. (The loader's last resort, `ldd --version` in a child process, is never run.)
127
- - **Windows x64:** an embedder that runs Node as a shared library (`node_target_type` is
128
- `shared_library`), or a Node whose `shlib_suffix` is `dll.a`, maps to the `win32-x64-gnu`
129
- platform, as in the loader. polyjose doesn't publish that addon, so those hosts can't load the
130
- package.
131
- - **Temporary directory:** each import that loads the addon creates one directory in
132
- `os.tmpdir()` (see "No swap after the check").
133
- - **`__napiBindingTarget`:** the loaded addon has an extra property, `__napiBindingTarget`
134
- (`"native"`), set by the loader. The package's own API doesn't export it.
135
-
136
- ## Secrets in results
137
-
138
- Results are plain objects, and their byte fields are plain `Uint8Array`s. So **printing a result
139
- prints its secret bytes**: `console.log`, `util.inspect`, `JSON.stringify` and error reporters
140
- show every byte. (The Kotlin and Swift bindings redact these fields when printed; JavaScript keeps
141
- plain data instead, ADR 0065.)
142
-
143
- - Never log a `JwsVerified` or `JweDecrypted` result.
144
- - Never log the options and records that carry a secret: `JwsVerifyOptions` (`detachedPayload`),
145
- `JweEncryptOptions` (`aad`), `JwsSigner` and `JweRecipient` (`key`). Never log a private JWK.
146
- - The seven secret fields are `JwsSigner.key`, `JweRecipient.key`, `JwsVerified.payload`,
147
- `JweDecrypted.plaintext`, `JweDecrypted.aad`, `JweEncryptOptions.aad` and
148
- `JwsVerifyOptions.detachedPayload`.
149
- - When you are done with a returned `Uint8Array`, wipe it with `fill(0)`. JavaScript strings, such
150
- as a JWK's text, can't be wiped.
89
+ - Getting started on Node.js: <https://polyjose.dev/start/node/>
90
+ - Platform support, how the addon is loaded, and handling secrets in results:
91
+ <https://polyjose.dev/reference/node/>
92
+ - Guides (sign and verify, encrypt and decrypt, validate a JWT, keys): <https://polyjose.dev>
93
+ - Error codes: <https://polyjose.dev/concepts/errors/>
94
+ - Source and issues: <https://github.com/bharathkontham/polyjose>
151
95
 
152
96
  ## License
153
97
 
154
- MIT; see `LICENSE`. `THIRD-PARTY-NOTICES.txt` lists the third-party code this package contains.
155
- Each platform package carries its own `THIRD-PARTY-NOTICES.txt` for the Rust crates compiled into
156
- its addon.
98
+ MIT; see [LICENSE](https://github.com/bharathkontham/polyjose/blob/main/LICENSE).
99
+ `THIRD-PARTY-NOTICES.txt` in the package lists the third-party code it contains.