castrum 0.7.0 → 0.9.1
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 +21 -0
- package/README.md +154 -368
- package/castrum.linux-x64-gnu.node +0 -0
- package/dist/index.d.ts +2763 -0
- package/dist/index.js +8707 -0
- package/docs/API.md +337 -0
- package/docs/ARCHITECTURE.md +507 -0
- package/docs/BENCHMARKS.md +343 -0
- package/docs/CASE_STUDY.md +240 -0
- package/docs/ENVIRONMENT.md +81 -0
- package/docs/FFI_BUN_GUIDE.md +327 -0
- package/docs/GETTING_STARTED.md +192 -0
- package/docs/INGRESS-ROUTER.md +81 -0
- package/docs/INGRESS.md +746 -0
- package/docs/NATIVE-ROUTE.md +53 -0
- package/docs/REPO_MAP.md +279 -0
- package/docs/adr/0001-two-wire-formats.md +39 -0
- package/docs/adr/0002-zero-dom-fast-schema.md +38 -0
- package/docs/adr/0003-bun-builtins-delegation.md +43 -0
- package/docs/bun-builtins-decision-matrix.md +115 -0
- package/index.ts +44 -8
- package/package.json +98 -33
- package/src/ingress/body.ts +289 -0
- package/src/ingress/constants.ts +220 -48
- package/src/ingress/context.ts +136 -0
- package/src/ingress/decode/baked-result.ts +121 -0
- package/src/ingress/decode/fast-result.ts +160 -0
- package/src/ingress/decode/packed-sections.ts +56 -0
- package/src/ingress/decode/result-base.ts +172 -0
- package/src/ingress/errors.ts +72 -0
- package/src/ingress/fast.ts +152 -1021
- package/src/ingress/handlers.ts +639 -1001
- package/src/ingress/headers/baked-templates.ts +102 -0
- package/src/ingress/headers/cors.ts +44 -0
- package/src/ingress/headers/fast-templates.ts +238 -0
- package/src/ingress/headers/hsts.ts +83 -0
- package/src/ingress/health.ts +68 -0
- package/src/ingress/index.ts +142 -405
- package/src/ingress/metrics.ts +132 -0
- package/src/ingress/options.ts +197 -0
- package/src/ingress/packing/gather-raw-headers.ts +139 -0
- package/src/ingress/packing/header-packing.ts +75 -0
- package/src/ingress/packing/input-packer.ts +164 -0
- package/src/ingress/packing/scratch.ts +50 -0
- package/src/ingress/packing/select-headers.ts +102 -0
- package/src/ingress/response/error-bodies.ts +75 -0
- package/src/ingress/response/terminal.ts +59 -0
- package/src/ingress/router.ts +237 -0
- package/src/ingress/routes/common.ts +79 -0
- package/src/ingress/routes/delete.ts +14 -0
- package/src/ingress/routes/echo.ts +107 -0
- package/src/ingress/routes/fallback.ts +23 -0
- package/src/ingress/routes/head.ts +27 -0
- package/src/ingress/routes/index.ts +11 -0
- package/src/ingress/routes/json-write.ts +129 -0
- package/src/ingress/routes/options.ts +26 -0
- package/src/ingress/routes/read.ts +35 -0
- package/src/ingress/routes/responder.ts +159 -0
- package/src/ingress/server-node.ts +376 -0
- package/src/ingress/server.ts +475 -0
- package/src/ingress/shared.ts +132 -0
- package/src/ingress/status.ts +97 -0
- package/src/ingress/types.ts +191 -0
- package/src/integration/batch.ts +58 -0
- package/src/integration/index.ts +29 -0
- package/src/integration/pipeline.ts +220 -0
- package/src/integration/streaming.ts +78 -0
- package/src/integration/websocket.ts +93 -0
- package/src/loader/batch.ts +137 -0
- package/src/loader/cost.ts +105 -0
- package/src/loader/index.ts +745 -0
- package/src/loader/ops.ts +657 -0
- package/src/loader/state.ts +84 -0
- package/src/loader/types.ts +145 -0
- package/src/native/ffi/constants.ts +58 -0
- package/src/native/ffi/selftest.ts +729 -0
- package/src/native/ffi/types.ts +474 -0
- package/src/native/ffi.ts +1775 -0
- package/src/native/index.ts +8 -207
- package/src/native/loader.ts +240 -0
- package/src/native/types/instances.ts +274 -0
- package/src/native/types.ts +401 -0
- package/src/runtime/builtins.ts +131 -0
- package/src/runtime/codec.ts +147 -0
- package/src/runtime/detect.ts +36 -0
- package/src/runtime/index.ts +28 -0
- package/src/runtime/native.ts +35 -0
- package/src/runtime/server.ts +31 -0
- package/src/runtime/transport.ts +64 -0
- package/src/runtime/types.ts +173 -0
- package/src/runtime/uuid.ts +45 -0
- package/src/runtime/websocket.ts +30 -0
- package/src/rust-ffi/addon.ts +10 -0
- package/src/rust-ffi/batch/build.ts +155 -0
- package/src/rust-ffi/batch/index.ts +8 -0
- package/src/rust-ffi/batch/types.ts +92 -0
- package/src/rust-ffi/client.ts +87 -0
- package/src/rust-ffi/context.ts +193 -0
- package/src/rust-ffi/index.ts +51 -541
- package/src/rust-ffi/into.ts +21 -0
- package/src/rust-ffi/options.ts +113 -0
- package/src/rust-ffi/packed.ts +253 -0
- package/src/rust-ffi/proven.ts +28 -0
- package/src/rust-ffi/scalar/crypto.ts +346 -0
- package/src/rust-ffi/scalar/factories.ts +459 -0
- package/src/rust-ffi/scalar/hashing.ts +74 -0
- package/src/rust-ffi/scalar/http.ts +337 -0
- package/src/rust-ffi/scalar/index.ts +31 -0
- package/src/rust-ffi/scalar/interface.ts +325 -0
- package/src/rust-ffi/scalar/json-packed.ts +175 -0
- package/src/rust-ffi/scalar/json.ts +61 -0
- package/src/rust-ffi/scalar/payload.ts +241 -0
- package/src/rust-ffi/text.ts +121 -0
- package/src/selection.json +194 -0
- package/src/selection.ts +73 -0
- package/src/shared/adaptive.ts +67 -0
- package/src/shared/buffer-pool.ts +264 -0
- package/src/shared/bytes.ts +108 -9
- package/src/shared/codec.ts +78 -0
- package/src/shared/env.ts +20 -0
- package/src/shared/log.ts +75 -0
- package/src/shared/metrics.ts +247 -0
- package/src/shared/packed/index.ts +10 -0
- package/src/shared/packed/parsers.ts +31 -0
- package/src/shared/packed/schema.ts +23 -0
- package/src/shared/packed/wire.ts +504 -0
- package/src/shared/proven.ts +289 -0
- package/src/shared/request-id.ts +67 -0
- package/src/shared/response.ts +104 -0
- package/src/shared/runtime.ts +29 -0
- package/src/shared/trace.ts +81 -0
- package/src/shared/uuid.ts +15 -0
- package/Cargo.toml +0 -73
- package/bench.ts +0 -3
- package/rust/batch.rs +0 -195
- package/rust/cookie_parser.rs +0 -70
- package/rust/cors.rs +0 -263
- package/rust/hashing.rs +0 -164
- package/rust/headers.rs +0 -234
- package/rust/hmac_sha256.rs +0 -113
- package/rust/http_parser.rs +0 -132
- package/rust/ingress.rs +0 -616
- package/rust/ingress_constants.rs +0 -89
- package/rust/ip_trust.rs +0 -185
- package/rust/json_ops.rs +0 -66
- package/rust/json_patch_ops.rs +0 -25
- package/rust/json_schema.rs +0 -151
- package/rust/json_ser.rs +0 -429
- package/rust/lib.rs +0 -48
- package/rust/method.rs +0 -141
- package/rust/mime_lookup.rs +0 -76
- package/rust/output.rs +0 -238
- package/rust/proxy.rs +0 -90
- package/rust/query_parser.rs +0 -141
- package/rust/random_token.rs +0 -52
- package/rust/rate_limit.rs +0 -203
- package/rust/terminal.rs +0 -95
- package/rust/unit_tests.rs +0 -843
- package/rust/url_codec.rs +0 -133
- package/rust/util.rs +0 -537
- package/rust/validation.rs +0 -107
- package/rust/websocket.rs +0 -23
- package/src/baseline/index.ts +0 -12
- package/src/baseline/tasks/cookie.ts +0 -22
- package/src/baseline/tasks/hashing.ts +0 -18
- package/src/baseline/tasks/hmac.ts +0 -38
- package/src/baseline/tasks/http.ts +0 -62
- package/src/baseline/tasks/json-patch.ts +0 -12
- package/src/baseline/tasks/json.ts +0 -50
- package/src/baseline/tasks/mime.ts +0 -5
- package/src/baseline/tasks/query.ts +0 -14
- package/src/baseline/tasks/token.ts +0 -15
- package/src/baseline/tasks/url.ts +0 -11
- package/src/baseline/tasks/validation.ts +0 -19
- package/src/baseline/tasks/websocket.ts +0 -14
- package/src/bench/assert.ts +0 -70
- package/src/bench/checks.ts +0 -227
- package/src/bench/checksum.ts +0 -27
- package/src/bench/comparisons.ts +0 -70
- package/src/bench/concurrent-worker.ts +0 -135
- package/src/bench/fixtures.ts +0 -233
- package/src/bench/index.ts +0 -2
- package/src/bench/measure.ts +0 -177
- package/src/bench/now.ts +0 -3
- package/src/bench/report.ts +0 -79
- package/src/bench/run.ts +0 -57
- package/src/bench/tasks/complex.ts +0 -232
- package/src/bench/tasks/concurrent.ts +0 -213
- package/src/bench/tasks/cookie.ts +0 -44
- package/src/bench/tasks/hashing.ts +0 -33
- package/src/bench/tasks/hmac.ts +0 -36
- package/src/bench/tasks/http.ts +0 -50
- package/src/bench/tasks/index.ts +0 -52
- package/src/bench/tasks/json-patch.ts +0 -21
- package/src/bench/tasks/json.ts +0 -33
- package/src/bench/tasks/mime.ts +0 -21
- package/src/bench/tasks/query.ts +0 -44
- package/src/bench/tasks/stress.ts +0 -142
- package/src/bench/tasks/token.ts +0 -20
- package/src/bench/tasks/url.ts +0 -34
- package/src/bench/tasks/validation.ts +0 -57
- package/src/bench/tasks/websocket.ts +0 -21
- package/src/bench/types.ts +0 -60
- package/src/data/json-rows.ts +0 -33
- package/src/ingress/packed-input.ts +0 -59
- package/src/shared/json.ts +0 -18
- package/src/shared/packed.ts +0 -316
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
// src/rust-ffi/scalar/crypto.ts — Auth / crypto scalar methods.
|
|
2
|
+
//
|
|
3
|
+
// Mirrors rust/crypto/*: base64/hex codecs, signed cookies, CSRF, random
|
|
4
|
+
// tokens, JWT, password hashing and AEAD.
|
|
5
|
+
//
|
|
6
|
+
// Runtime dispatch is centralized in the adapter (`ctx.runtime`): Bun built-in
|
|
7
|
+
// delegations (randomToken/base64*/hexEncode) come from `builtins.has(op)` and
|
|
8
|
+
// the native call from `transport.resolve(op)` / `transport.ffi` (bun:ffi
|
|
9
|
+
// first, napi fallback) — no inline `isBun()` / `getBunFFI()`.
|
|
10
|
+
|
|
11
|
+
import type { PasswordHashOptions } from '../../native'
|
|
12
|
+
import { encoder } from '../../shared/bytes'
|
|
13
|
+
import { writeInto } from '../into'
|
|
14
|
+
import { memoizeFfi, type RustClientContext, resolveNative } from '../context'
|
|
15
|
+
|
|
16
|
+
/** Auth / crypto scalar methods (`Pick<RustScalar, ...>`). */
|
|
17
|
+
export function buildCrypto(ctx: RustClientContext) {
|
|
18
|
+
const { addon } = ctx
|
|
19
|
+
const { builtins, transport } = ctx.runtime
|
|
20
|
+
// Hoist the immutable per-runtime builtin-delegation decisions into a
|
|
21
|
+
// bind-time map (BUILTIN_OPS is a module constant — never mutated after
|
|
22
|
+
// load) so the hot path reads object properties instead of `Set.has`.
|
|
23
|
+
const HAS = {
|
|
24
|
+
randomToken: builtins.has('randomToken'),
|
|
25
|
+
base64Encode: builtins.has('base64Encode'),
|
|
26
|
+
base64UrlEncode: builtins.has('base64UrlEncode'),
|
|
27
|
+
hexEncode: builtins.has('hexEncode'),
|
|
28
|
+
}
|
|
29
|
+
// Lazy-memoized ffi surface: binds on first call, single local read after.
|
|
30
|
+
const ffi = memoizeFfi(transport)
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
randomToken(byteLen: number): Uint8Array | string {
|
|
34
|
+
// Optimal by default under Bun: `crypto.getRandomValues` + native hex
|
|
35
|
+
// beats the rust+FFI crossing for token-sized draws (decision matrix);
|
|
36
|
+
// the builtin carries the 16 MiB guard and returns the hex STRING. Node
|
|
37
|
+
// keeps the addon.
|
|
38
|
+
if (HAS.randomToken) return builtins.randomToken(byteLen)
|
|
39
|
+
return resolveNative(ctx, 'randomToken')(byteLen) as Uint8Array | string
|
|
40
|
+
},
|
|
41
|
+
randomTokenInto(byteLen: number, output: Uint8Array): number {
|
|
42
|
+
// Pooled sibling — writes `byteLen*2` hex chars into `output`. FFI path
|
|
43
|
+
// uses the native `_into` (no cstring round-trip); without ffi the Bun
|
|
44
|
+
// builtin (or addon) hex is written into the caller buffer.
|
|
45
|
+
const f = ffi()
|
|
46
|
+
if (f) return f.randomTokenInto(byteLen, output)
|
|
47
|
+
if (HAS.randomToken) {
|
|
48
|
+
const need = byteLen * 2
|
|
49
|
+
if (output.length < need) throw new Error('random token: output buffer too small')
|
|
50
|
+
output.set(encoder.encode(builtins.randomToken(byteLen) as string))
|
|
51
|
+
return need
|
|
52
|
+
}
|
|
53
|
+
const bytes = addon.randomToken(byteLen)
|
|
54
|
+
return writeInto('random token', output, bytes)
|
|
55
|
+
},
|
|
56
|
+
base64Encode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array | string {
|
|
57
|
+
// Bun's Buffer base64 (SIMD) beats the rust+FFI crossing (~2x, measured)
|
|
58
|
+
// for the standard (url-safe=false, padded) case; the builtin returns
|
|
59
|
+
// `undefined` for url-safe/unpadded so those fall through to native.
|
|
60
|
+
// Parity pinned by test/unit/features/encoding.test.ts.
|
|
61
|
+
if (HAS.base64Encode) {
|
|
62
|
+
const encoded = builtins.base64Encode(input, urlSafe, padding)
|
|
63
|
+
if (encoded !== undefined) return encoded
|
|
64
|
+
}
|
|
65
|
+
return resolveNative(ctx, 'base64Encode')(
|
|
66
|
+
input,
|
|
67
|
+
urlSafe ?? undefined,
|
|
68
|
+
padding ?? undefined,
|
|
69
|
+
) as Uint8Array | string
|
|
70
|
+
},
|
|
71
|
+
base64EncodeInto(
|
|
72
|
+
input: Uint8Array,
|
|
73
|
+
output: Uint8Array,
|
|
74
|
+
urlSafe?: boolean,
|
|
75
|
+
padding?: boolean,
|
|
76
|
+
): number {
|
|
77
|
+
return resolveNative(ctx, 'base64EncodeInto')(
|
|
78
|
+
input,
|
|
79
|
+
output,
|
|
80
|
+
urlSafe ?? undefined,
|
|
81
|
+
padding ?? undefined,
|
|
82
|
+
) as number
|
|
83
|
+
},
|
|
84
|
+
base64Decode(input: Uint8Array, urlSafe?: boolean, padding?: boolean): Uint8Array {
|
|
85
|
+
return resolveNative(ctx, 'base64Decode')(
|
|
86
|
+
input,
|
|
87
|
+
urlSafe ?? undefined,
|
|
88
|
+
padding ?? undefined,
|
|
89
|
+
) as Uint8Array
|
|
90
|
+
},
|
|
91
|
+
base64DecodeInto(
|
|
92
|
+
input: Uint8Array,
|
|
93
|
+
output: Uint8Array,
|
|
94
|
+
urlSafe?: boolean,
|
|
95
|
+
padding?: boolean,
|
|
96
|
+
): number {
|
|
97
|
+
return resolveNative(ctx, 'base64DecodeInto')(
|
|
98
|
+
input,
|
|
99
|
+
output,
|
|
100
|
+
urlSafe ?? undefined,
|
|
101
|
+
padding ?? undefined,
|
|
102
|
+
) as number
|
|
103
|
+
},
|
|
104
|
+
base64UrlEncode(input: Uint8Array): Uint8Array | string {
|
|
105
|
+
// Bun's native `Buffer.toString('base64url')` (SIMD) beats the rust+FFI
|
|
106
|
+
// crossing — same delegation rationale as `base64Encode`; verified
|
|
107
|
+
// byte-parity with the Rust `URL_SAFE_NO_PAD` engine
|
|
108
|
+
// (test/unit/features/encoding.test.ts). The ffi path reuses
|
|
109
|
+
// `base64Encode(input, true, false)` (no dedicated base64UrlEncode symbol).
|
|
110
|
+
if (HAS.base64UrlEncode) return builtins.base64UrlEncode(input)
|
|
111
|
+
const f = ffi()
|
|
112
|
+
if (f) return f.base64Encode(input, true, false)
|
|
113
|
+
return addon.base64UrlEncode(input)
|
|
114
|
+
},
|
|
115
|
+
base64UrlDecode(input: Uint8Array): Uint8Array {
|
|
116
|
+
const f = ffi()
|
|
117
|
+
if (f) return f.base64Decode(input, true, false)
|
|
118
|
+
return addon.base64UrlDecode(input)
|
|
119
|
+
},
|
|
120
|
+
hexEncode(input: Uint8Array): Uint8Array | string {
|
|
121
|
+
// Bun's native `Buffer.toString('hex')` (SIMD) beats the rust+FFI
|
|
122
|
+
// crossing, especially for large inputs. Verified byte-parity with the
|
|
123
|
+
// Rust hex encoder. Bun returns the hex STRING; Node keeps the addon bytes.
|
|
124
|
+
if (HAS.hexEncode) return builtins.hexEncode(input)
|
|
125
|
+
return resolveNative(ctx, 'hexEncode')(input) as Uint8Array | string
|
|
126
|
+
},
|
|
127
|
+
hexEncodeInto(input: Uint8Array, output: Uint8Array): number {
|
|
128
|
+
return resolveNative(ctx, 'hexEncodeInto')(input, output) as number
|
|
129
|
+
},
|
|
130
|
+
hexDecode(input: Uint8Array): Uint8Array {
|
|
131
|
+
return resolveNative(ctx, 'hexDecode')(input) as Uint8Array
|
|
132
|
+
},
|
|
133
|
+
hexDecodeInto(input: Uint8Array, output: Uint8Array): number {
|
|
134
|
+
return resolveNative(ctx, 'hexDecodeInto')(input, output) as number
|
|
135
|
+
},
|
|
136
|
+
signCookie(value: Uint8Array, secret: Uint8Array): Uint8Array | string {
|
|
137
|
+
return resolveNative(ctx, 'signCookie')(value, secret) as Uint8Array | string
|
|
138
|
+
},
|
|
139
|
+
signCookieInto(value: Uint8Array, secret: Uint8Array, output: Uint8Array): number {
|
|
140
|
+
const f = ffi()
|
|
141
|
+
if (f) return f.signCookieInto(value, secret, output)
|
|
142
|
+
return writeInto('sign cookie', output, addon.signCookie(value, secret))
|
|
143
|
+
},
|
|
144
|
+
verifyCookie(signed: Uint8Array, secret: Uint8Array): Uint8Array | string | null {
|
|
145
|
+
return resolveNative(ctx, 'verifyCookie')(signed, secret) as Uint8Array | string | null
|
|
146
|
+
},
|
|
147
|
+
verifyCookieInto(signed: Uint8Array, secret: Uint8Array, output: Uint8Array): number | null {
|
|
148
|
+
const f = ffi()
|
|
149
|
+
if (f) return f.verifyCookieInto(signed, secret, output)
|
|
150
|
+
const value = addon.verifyCookie(signed, secret)
|
|
151
|
+
if (value === null) return null
|
|
152
|
+
return writeInto('verify cookie', output, value)
|
|
153
|
+
},
|
|
154
|
+
csrfToken(secret: Uint8Array): Uint8Array | string {
|
|
155
|
+
return resolveNative(ctx, 'csrfToken')(secret) as Uint8Array | string
|
|
156
|
+
},
|
|
157
|
+
csrfTokenInto(secret: Uint8Array, output: Uint8Array): number {
|
|
158
|
+
const f = ffi()
|
|
159
|
+
if (f) return f.csrfTokenInto(secret, output)
|
|
160
|
+
return writeInto('csrf token', output, addon.csrfToken(secret))
|
|
161
|
+
},
|
|
162
|
+
csrfVerify(token: Uint8Array, secret: Uint8Array): boolean {
|
|
163
|
+
return resolveNative(ctx, 'csrfVerify')(token, secret) as boolean
|
|
164
|
+
},
|
|
165
|
+
jwtSign(
|
|
166
|
+
claims: Record<string, unknown>,
|
|
167
|
+
secret: Uint8Array,
|
|
168
|
+
ttlSeconds?: number | null,
|
|
169
|
+
nowSeconds?: number,
|
|
170
|
+
): Uint8Array {
|
|
171
|
+
// FFI-first: route the object claims through the C-ABI `jwtSignBytes`
|
|
172
|
+
// (pre-serialized JSON). The engine returns the token as a string
|
|
173
|
+
// (native transfer); re-encode to bytes to keep the public BYTE contract
|
|
174
|
+
// (batch/benchmark consumers compare bytes). NOTE: JSON.stringify
|
|
175
|
+
// preserves JS insertion order while napi's serde_json sorts keys — for
|
|
176
|
+
// multi-key claims the two transports emit different-but-equally-valid
|
|
177
|
+
// tokens (both verify; parity tests cross-verify semantically).
|
|
178
|
+
const f = ffi()
|
|
179
|
+
if (f) {
|
|
180
|
+
const token = f.jwtSignBytes(
|
|
181
|
+
encoder.encode(JSON.stringify(claims)),
|
|
182
|
+
secret,
|
|
183
|
+
ttlSeconds ?? 0,
|
|
184
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
185
|
+
)
|
|
186
|
+
return encoder.encode(token)
|
|
187
|
+
}
|
|
188
|
+
return addon.jwtSign(
|
|
189
|
+
claims,
|
|
190
|
+
secret,
|
|
191
|
+
ttlSeconds ?? null,
|
|
192
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
193
|
+
)
|
|
194
|
+
},
|
|
195
|
+
jwtSignBytes(
|
|
196
|
+
claimsJson: Uint8Array,
|
|
197
|
+
secret: Uint8Array,
|
|
198
|
+
ttlSeconds?: number | null,
|
|
199
|
+
nowSeconds?: number,
|
|
200
|
+
): Uint8Array | string {
|
|
201
|
+
const f = ffi()
|
|
202
|
+
if (f) {
|
|
203
|
+
// The C-ABI `ttl <= 0` sentinel matches napi's Option<i64> (inject only
|
|
204
|
+
// when positive), and nowSeconds defaults identically.
|
|
205
|
+
return f.jwtSignBytes(
|
|
206
|
+
claimsJson,
|
|
207
|
+
secret,
|
|
208
|
+
ttlSeconds ?? 0,
|
|
209
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
210
|
+
)
|
|
211
|
+
}
|
|
212
|
+
return addon.jwtSignBytes(
|
|
213
|
+
claimsJson,
|
|
214
|
+
secret,
|
|
215
|
+
ttlSeconds ?? null,
|
|
216
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
217
|
+
)
|
|
218
|
+
},
|
|
219
|
+
jwtSignBytesInto(
|
|
220
|
+
claimsJson: Uint8Array,
|
|
221
|
+
secret: Uint8Array,
|
|
222
|
+
output: Uint8Array,
|
|
223
|
+
ttlSeconds?: number | null,
|
|
224
|
+
nowSeconds?: number,
|
|
225
|
+
): number {
|
|
226
|
+
const f = ffi()
|
|
227
|
+
if (f) {
|
|
228
|
+
return f.jwtSignBytesInto(
|
|
229
|
+
claimsJson,
|
|
230
|
+
secret,
|
|
231
|
+
ttlSeconds ?? 0,
|
|
232
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
233
|
+
output,
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
return writeInto(
|
|
237
|
+
'jwt sign',
|
|
238
|
+
output,
|
|
239
|
+
addon.jwtSignBytes(
|
|
240
|
+
claimsJson,
|
|
241
|
+
secret,
|
|
242
|
+
ttlSeconds ?? null,
|
|
243
|
+
nowSeconds ?? Math.floor(Date.now() / 1000),
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
},
|
|
247
|
+
jwtVerify(token: Uint8Array, secret: Uint8Array, nowSeconds?: number): unknown {
|
|
248
|
+
const f = ffi()
|
|
249
|
+
if (f) {
|
|
250
|
+
// Verify via the FFI cstring path (castrum_jwt_verify): the engine
|
|
251
|
+
// clones the claims JSON string at return (zero decode), and `null` =
|
|
252
|
+
// invalid signature / expired / malformed → null (napi Option parity).
|
|
253
|
+
const claims = f.jwtVerify(token, secret, nowSeconds ?? Math.floor(Date.now() / 1000))
|
|
254
|
+
return claims === null ? null : (JSON.parse(claims) as unknown)
|
|
255
|
+
}
|
|
256
|
+
return addon.jwtVerify(token, secret, nowSeconds ?? Math.floor(Date.now() / 1000))
|
|
257
|
+
},
|
|
258
|
+
passwordHash(
|
|
259
|
+
password: Uint8Array,
|
|
260
|
+
salt: Uint8Array,
|
|
261
|
+
options?: PasswordHashOptions | null,
|
|
262
|
+
): Uint8Array | string {
|
|
263
|
+
const f = ffi()
|
|
264
|
+
if (f) {
|
|
265
|
+
// Resolve the napi defaults (rust/crypto/argon2.rs resolve_opts).
|
|
266
|
+
const o = options ?? {}
|
|
267
|
+
return f.passwordHash(
|
|
268
|
+
password,
|
|
269
|
+
salt,
|
|
270
|
+
o.mCost ?? 19456,
|
|
271
|
+
o.tCost ?? 2,
|
|
272
|
+
o.pCost ?? 1,
|
|
273
|
+
o.outLen ?? 32,
|
|
274
|
+
)
|
|
275
|
+
}
|
|
276
|
+
return addon.passwordHash(password, salt, options ?? null)
|
|
277
|
+
},
|
|
278
|
+
passwordVerify(password: Uint8Array, phc: Uint8Array): boolean {
|
|
279
|
+
return resolveNative(ctx, 'passwordVerify')(password, phc) as boolean
|
|
280
|
+
},
|
|
281
|
+
passwordHashBcrypt(password: Uint8Array, cost: number): string {
|
|
282
|
+
return resolveNative(ctx, 'passwordHashBcrypt')(password, cost) as string
|
|
283
|
+
},
|
|
284
|
+
passwordVerifyBcrypt(password: Uint8Array, hash: string): boolean {
|
|
285
|
+
// FFI-first: `hash` is a `cstring` ARG (the engine transcodes the PHC
|
|
286
|
+
// string in-engine — no JS encode); `password` stays `(ptr,len)` bytes.
|
|
287
|
+
const f = ffi()
|
|
288
|
+
if (f) return f.passwordVerifyBcrypt(password, hash)
|
|
289
|
+
return addon.passwordVerifyBcrypt(password, hash)
|
|
290
|
+
},
|
|
291
|
+
pbkdf2Sha256(
|
|
292
|
+
password: Uint8Array,
|
|
293
|
+
salt: Uint8Array,
|
|
294
|
+
rounds: number,
|
|
295
|
+
dkLen: number,
|
|
296
|
+
): Uint8Array {
|
|
297
|
+
return resolveNative(ctx, 'pbkdf2Sha256')(password, salt, rounds, dkLen) as Uint8Array
|
|
298
|
+
},
|
|
299
|
+
aeadEncrypt(
|
|
300
|
+
key: Uint8Array,
|
|
301
|
+
nonce: Uint8Array,
|
|
302
|
+
plaintext: Uint8Array,
|
|
303
|
+
algorithm?: string | null,
|
|
304
|
+
): Uint8Array {
|
|
305
|
+
const f = ffi()
|
|
306
|
+
if (f) {
|
|
307
|
+
return f.aeadEncrypt(key, nonce, plaintext, algorithm === 'chacha20-poly1305' ? 1 : 0)
|
|
308
|
+
}
|
|
309
|
+
return addon.aeadEncrypt(key, nonce, plaintext, algorithm ?? null)
|
|
310
|
+
},
|
|
311
|
+
aeadEncryptInto(
|
|
312
|
+
key: Uint8Array,
|
|
313
|
+
nonce: Uint8Array,
|
|
314
|
+
plaintext: Uint8Array,
|
|
315
|
+
output: Uint8Array,
|
|
316
|
+
algorithm?: string | null,
|
|
317
|
+
): number {
|
|
318
|
+
const f = ffi()
|
|
319
|
+
if (f) {
|
|
320
|
+
return f.aeadEncryptInto(
|
|
321
|
+
key,
|
|
322
|
+
nonce,
|
|
323
|
+
plaintext,
|
|
324
|
+
output,
|
|
325
|
+
algorithm === 'chacha20-poly1305' ? 1 : 0,
|
|
326
|
+
)
|
|
327
|
+
}
|
|
328
|
+
const bytes = addon.aeadEncrypt(key, nonce, plaintext, algorithm ?? null)
|
|
329
|
+
if (output.length < bytes.length) throw new Error('aead encrypt: output buffer too small')
|
|
330
|
+
output.set(bytes)
|
|
331
|
+
return bytes.length
|
|
332
|
+
},
|
|
333
|
+
aeadDecrypt(
|
|
334
|
+
key: Uint8Array,
|
|
335
|
+
nonce: Uint8Array,
|
|
336
|
+
ciphertext: Uint8Array,
|
|
337
|
+
algorithm?: string | null,
|
|
338
|
+
): Uint8Array | null {
|
|
339
|
+
const f = ffi()
|
|
340
|
+
if (f) {
|
|
341
|
+
return f.aeadDecrypt(key, nonce, ciphertext, algorithm === 'chacha20-poly1305' ? 1 : 0)
|
|
342
|
+
}
|
|
343
|
+
return addon.aeadDecrypt(key, nonce, ciphertext, algorithm ?? null)
|
|
344
|
+
},
|
|
345
|
+
}
|
|
346
|
+
}
|