@polyjose/node 0.1.1 → 0.1.2
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 +80 -137
- package/THIRD-PARTY-NOTICES.txt +15966 -40
- package/addon-sha256.json +8 -8
- package/backend.d.ts +71 -75
- package/backend.js +124 -249
- package/package.json +9 -13
- package/polyjose.darwin-arm64.node +0 -0
- package/polyjose.darwin-x64.node +0 -0
- package/polyjose.linux-arm64-gnu.node +0 -0
- package/polyjose.linux-arm64-musl.node +0 -0
- package/polyjose.linux-x64-gnu.node +0 -0
- package/polyjose.linux-x64-musl.node +0 -0
- package/polyjose.win32-arm64-msvc.node +0 -0
- package/polyjose.win32-x64-msvc.node +0 -0
- package/binding.cjs +0 -869
- package/binding.d.ts +0 -469
package/README.md
CHANGED
|
@@ -11,146 +11,89 @@ accepted (RFC 8725).
|
|
|
11
11
|
npm install @polyjose/node
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
|
155
|
-
|
|
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.
|