@pulse-compute/jwt 0.0.0 → 1.0.0-beta.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.
Files changed (60) hide show
  1. package/README.md +58 -3
  2. package/as/index.as.ts +388 -0
  3. package/conformance/native-contract.json +53 -0
  4. package/dist/bearer.d.ts +4 -0
  5. package/dist/bearer.d.ts.map +1 -0
  6. package/dist/bearer.js +33 -0
  7. package/dist/bearer.js.map +1 -0
  8. package/dist/crypto-verifier.d.ts +19 -0
  9. package/dist/crypto-verifier.d.ts.map +1 -0
  10. package/dist/crypto-verifier.js +225 -0
  11. package/dist/crypto-verifier.js.map +1 -0
  12. package/dist/errors.d.ts +14 -0
  13. package/dist/errors.d.ts.map +1 -0
  14. package/dist/errors.js +65 -0
  15. package/dist/errors.js.map +1 -0
  16. package/dist/index.d.ts +19 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +21 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/internal/clock.d.ts +7 -0
  21. package/dist/internal/clock.d.ts.map +1 -0
  22. package/dist/internal/clock.js +34 -0
  23. package/dist/internal/clock.js.map +1 -0
  24. package/dist/internal/data.d.ts +12 -0
  25. package/dist/internal/data.d.ts.map +1 -0
  26. package/dist/internal/data.js +86 -0
  27. package/dist/internal/data.js.map +1 -0
  28. package/dist/internal/package-runtime.d.ts +9 -0
  29. package/dist/internal/package-runtime.d.ts.map +1 -0
  30. package/dist/internal/package-runtime.js +14 -0
  31. package/dist/internal/package-runtime.js.map +1 -0
  32. package/dist/internal/registered-claims.d.ts +3 -0
  33. package/dist/internal/registered-claims.d.ts.map +1 -0
  34. package/dist/internal/registered-claims.js +84 -0
  35. package/dist/internal/registered-claims.js.map +1 -0
  36. package/dist/internal/verifier-authority.d.ts +24 -0
  37. package/dist/internal/verifier-authority.d.ts.map +1 -0
  38. package/dist/internal/verifier-authority.js +101 -0
  39. package/dist/internal/verifier-authority.js.map +1 -0
  40. package/dist/options.d.ts +113 -0
  41. package/dist/options.d.ts.map +1 -0
  42. package/dist/options.js +382 -0
  43. package/dist/options.js.map +1 -0
  44. package/dist/provider.d.ts +7 -0
  45. package/dist/provider.d.ts.map +1 -0
  46. package/dist/provider.js +7 -0
  47. package/dist/provider.js.map +1 -0
  48. package/dist/result.d.ts +17 -0
  49. package/dist/result.d.ts.map +1 -0
  50. package/dist/result.js +96 -0
  51. package/dist/result.js.map +1 -0
  52. package/dist/token.d.ts +20 -0
  53. package/dist/token.d.ts.map +1 -0
  54. package/dist/token.js +323 -0
  55. package/dist/token.js.map +1 -0
  56. package/package.json +64 -6
  57. package/pulse.package.json +75 -0
  58. package/pulsewasm.compiler.cjs +1258 -0
  59. package/pulsewasm.manifest.cjs +117 -0
  60. package/pulsewasm.native.cjs +60 -0
package/README.md CHANGED
@@ -1,5 +1,60 @@
1
- # @pulse-compute/jwt
1
+ # `@pulse-compute/jwt`
2
2
 
3
- > Bootstrap placeholder — no runtime or API.
3
+ <!-- pulse-package-status:start -->
4
+ > **Support tier:** Supported provider/extension surface<br>
5
+ > **Audience:** Applications verifying bounded JWT bearer credentials through Pulse providers.<br>
6
+ > **Install directly:** Yes, when an application verifies JWTs.<br>
7
+ > **Supported entry points:** `@pulse-compute/jwt`<br>
8
+ > **Stability:** The package root is the supported provider-neutral verification contract; provider and compiler integration subpaths are not application APIs.<br>
9
+ > **Canonical documentation:** [Package guide](https://pulsecompute.io/v1.0.0-beta.1/packages/jwt/)
10
+ >
11
+ > This release-status block is generated from the synchronized `Pulse 1.0.0-beta.1` package policy.
12
+ <!-- pulse-package-status:end -->
4
13
 
5
- This inert 0.0.0 package reserves the npm name for future Pulse releases.
14
+ Bounded, provider-neutral JWT verification for Pulse handlers.
15
+
16
+ This package and `@pulse-compute/crypto` are members of the synchronized
17
+ `1.0.0-beta.1` release catalog.
18
+
19
+ Verification is implemented for HS256 in all four sealed target cells. G3 also
20
+ activates ES256 through the crypto-owned signature seam. H4 aligns Node and
21
+ Fastly JavaScript on the runtime-builtin realization and both Native providers
22
+ on the exact guest-linked composition. No target probes for a realization and
23
+ no failure falls back to a different key, algorithm, backend, target, or
24
+ provider.
25
+
26
+ Applications import the package root:
27
+
28
+ ```ts
29
+ import { jwt } from '@pulse-compute/jwt'
30
+
31
+ const verified = await jwt.verify(
32
+ ctx,
33
+ jwt.bearer(ctx.req),
34
+ {
35
+ algorithms: ['HS256'],
36
+ key: { type: 'secret', binding: 'JWT_SECRET' },
37
+ issuer: 'https://issuer.example',
38
+ audience: 'pulse-api',
39
+ },
40
+ )
41
+ ```
42
+
43
+ ES256 accepts only inline public P-256 JWKs or static JWKS values with at most
44
+ 16 entries. Coordinates are canonical unpadded base64url encodings of exactly
45
+ 32 bytes. Private and certificate members are rejected; `kid` selection is
46
+ deterministic; duplicate, missing, ambiguous, and unknown selections fail
47
+ closed. There is no remote discovery, fetch, cache, or refresh path.
48
+
49
+ The provider resolves the request-owned secret and passes only bounded bytes to
50
+ `@pulse-compute/crypto`. JWT authenticates the compact JWS before exposing
51
+ claims, captures one wall-clock instant only after authenticity, evaluates
52
+ registered claims before an optional runtime schema, and returns a detached,
53
+ deeply frozen result. It does not authorize application or host behavior.
54
+
55
+ RS256, EdDSA, signing, remote key authority, custom crypto providers, and
56
+ automatic fallback remain unavailable. The shared G3 ES256 corpus and
57
+ package-to-crypto composition are recorded in
58
+ `wasm/.test-results/jwt-g3/jwt-g3-evidence.json`; the six-cell target matrix is
59
+ recorded in `wasm/.test-results/boundary-h4/es256-six-cell-matrix.json`. The
60
+ prior four-cell HS256 seal remains historical regression evidence.
package/as/index.as.ts ADDED
@@ -0,0 +1,388 @@
1
+ /*
2
+ * Pulse package-owned Native JWT verifier.
3
+ *
4
+ * This source is concatenated into a provider's one final AssemblyScript
5
+ * module. It owns compact-JWS parsing, protected-header policy, registered
6
+ * claim validation, result shaping, and the ordered composition boundary to
7
+ * @pulse-compute/crypto. Provider callbacks retain request-owned secret,
8
+ * wall-clock, schema, redaction, and error-transport authority.
9
+ */
10
+
11
+ export const PULSE_JWT_SIDECAR_ABI_VERSION: i32 = 2
12
+ export const PULSE_JWT_VERIFY_CRYPTO_COMPOSITION_REQUIRED: i32 = -2
13
+
14
+ const __PULSE_JWT_TOKEN_BYTES_MAX: i32 = 16 * 1024
15
+ const __PULSE_JWT_HEADER_BYTES_MAX: i32 = 4 * 1024
16
+ const __PULSE_JWT_CLAIMS_BYTES_MAX: i32 = 16 * 1024
17
+ const __PULSE_JWT_HS256_SIGNATURE_BYTES: i32 = 32
18
+ const __PULSE_JWT_ES256_SIGNATURE_BYTES: i32 = 64
19
+ const __PULSE_JWT_KEY_BYTES_MIN: i32 = 32
20
+ const __PULSE_JWT_KEY_BYTES_MAX: i32 = 4096
21
+ const __PULSE_JWT_ES256_PUBLIC_KEY_BYTES: i32 = 64
22
+
23
+ const __PULSE_JWT_ERROR_TOKEN_REQUIRED: i32 = 1
24
+ const __PULSE_JWT_ERROR_BEARER_INVALID: i32 = 2
25
+ const __PULSE_JWT_ERROR_MALFORMED: i32 = 3
26
+ const __PULSE_JWT_ERROR_LIMIT_EXCEEDED: i32 = 4
27
+ const __PULSE_JWT_ERROR_ALGORITHM_NOT_ALLOWED: i32 = 5
28
+ const __PULSE_JWT_ERROR_KEY_INVALID: i32 = 6
29
+ const __PULSE_JWT_ERROR_SIGNATURE_INVALID: i32 = 7
30
+ const __PULSE_JWT_ERROR_CLOCK_INVALID: i32 = 8
31
+ const __PULSE_JWT_ERROR_CLAIMS_INVALID: i32 = 9
32
+ const __PULSE_JWT_ERROR_CLAIMS_SCHEMA_INVALID: i32 = 10
33
+ const __PULSE_JWT_ERROR_OPERATION_FAILED: i32 = 11
34
+
35
+ function __pulse_jwt_fail(effectIndex: i32, code: i32, stage: i32): i32 {
36
+ return __pulse_fastly_jwt_fail_code(effectIndex, code, stage)
37
+ }
38
+
39
+ function __pulse_jwt_base64url_value(code: i32): i32 {
40
+ if (code >= 65 && code <= 90) return code - 65
41
+ if (code >= 97 && code <= 122) return code - 71
42
+ if (code >= 48 && code <= 57) return code + 4
43
+ if (code == 45) return 62
44
+ if (code == 95) return 63
45
+ return -1
46
+ }
47
+
48
+ function __pulse_jwt_base64url_decode(segment: string): Uint8Array | null {
49
+ if (segment.length == 0 || segment.length % 4 == 1) return null
50
+ const remainder = segment.length % 4
51
+ const last = __pulse_jwt_base64url_value(segment.charCodeAt(segment.length - 1))
52
+ if (last < 0 || (remainder == 2 && (last & 15) != 0) || (remainder == 3 && (last & 3) != 0)) return null
53
+ const output = new Uint8Array((segment.length * 6) >> 3)
54
+ let accumulator: u32 = 0
55
+ let bits: i32 = 0
56
+ let outputIndex: i32 = 0
57
+ for (let index: i32 = 0; index < segment.length; index += 1) {
58
+ const value = __pulse_jwt_base64url_value(segment.charCodeAt(index))
59
+ if (value < 0) return null
60
+ accumulator = (accumulator << 6) | <u32>value
61
+ bits += 6
62
+ if (bits >= 8) {
63
+ bits -= 8
64
+ unchecked(output[outputIndex] = <u8>(accumulator >> bits))
65
+ outputIndex += 1
66
+ }
67
+ }
68
+ return output
69
+ }
70
+
71
+ function __pulse_jwt_decode_text(bytes: Uint8Array): string {
72
+ return String.UTF8.decodeUnsafe(bytes.dataStart, bytes.byteLength, false)
73
+ }
74
+
75
+ function __pulse_jwt_wipe(bytes: Uint8Array): void {
76
+ for (let index: i32 = 0; index < bytes.byteLength; index += 1) unchecked(bytes[index] = 0)
77
+ }
78
+
79
+ function __pulse_jwt_object_field(object: __PulseFastlyValue, name: string): i32 {
80
+ const index = __pulse_fastly_find(object, name)
81
+ return index < 0 ? host_value_undefined() : unchecked(object.values[index])
82
+ }
83
+
84
+ function __pulse_jwt_has_field(object: __PulseFastlyValue, name: string): bool {
85
+ return __pulse_fastly_find(object, name) >= 0
86
+ }
87
+
88
+ function __pulse_jwt_string_member(object: __PulseFastlyValue, name: string): string | null {
89
+ const index = __pulse_fastly_find(object, name)
90
+ if (index < 0) return null
91
+ const value = __pulse_fastly_value(unchecked(object.values[index]))
92
+ return value.kind == PULSE_VALUE_STRING ? value.text : null
93
+ }
94
+
95
+ function __pulse_jwt_array_contains(array: __PulseFastlyValue, expected: string): bool {
96
+ if (array.kind != PULSE_VALUE_ARRAY) return false
97
+ for (let index: i32 = 0; index < array.values.length; index += 1) {
98
+ const value = __pulse_fastly_value(unchecked(array.values[index]))
99
+ if (value.kind == PULSE_VALUE_STRING && value.text == expected) return true
100
+ }
101
+ return false
102
+ }
103
+
104
+ function __pulse_jwt_config_accepts(handle: i32, candidate: string): bool {
105
+ const configured = __pulse_fastly_value(handle)
106
+ if (configured.kind == PULSE_VALUE_STRING) return configured.text == candidate
107
+ return __pulse_jwt_array_contains(configured, candidate)
108
+ }
109
+
110
+ function __pulse_jwt_audience_valid(claims: __PulseFastlyValue, policy: __PulseFastlyValue): bool {
111
+ const policyIndex = __pulse_fastly_find(policy, "audience")
112
+ if (policyIndex < 0) return true
113
+ const audienceIndex = __pulse_fastly_find(claims, "aud")
114
+ if (audienceIndex < 0) return false
115
+ const audience = __pulse_fastly_value(unchecked(claims.values[audienceIndex]))
116
+ if (audience.kind == PULSE_VALUE_STRING) {
117
+ return __pulse_jwt_config_accepts(unchecked(policy.values[policyIndex]), audience.text)
118
+ }
119
+ if (audience.kind != PULSE_VALUE_ARRAY) return false
120
+ for (let index: i32 = 0; index < audience.values.length; index += 1) {
121
+ const value = __pulse_fastly_value(unchecked(audience.values[index]))
122
+ if (value.kind != PULSE_VALUE_STRING) return false
123
+ if (__pulse_jwt_config_accepts(unchecked(policy.values[policyIndex]), value.text)) return true
124
+ }
125
+ return false
126
+ }
127
+
128
+ function __pulse_jwt_numeric_claim(claims: __PulseFastlyValue, name: string): f64 {
129
+ const index = __pulse_fastly_find(claims, name)
130
+ if (index < 0) return NaN
131
+ const value = __pulse_fastly_value(unchecked(claims.values[index]))
132
+ return value.kind == PULSE_VALUE_NUMBER ? value.number : NaN
133
+ }
134
+
135
+ function __pulse_jwt_validate_registered(
136
+ claims: __PulseFastlyValue,
137
+ policy: __PulseFastlyValue,
138
+ now: f64,
139
+ ): bool {
140
+ const requiredIndex = __pulse_fastly_find(policy, "requiredClaims")
141
+ if (requiredIndex >= 0) {
142
+ const required = __pulse_fastly_value(unchecked(policy.values[requiredIndex]))
143
+ if (required.kind != PULSE_VALUE_ARRAY) return false
144
+ for (let index: i32 = 0; index < required.values.length; index += 1) {
145
+ const name = __pulse_fastly_value(unchecked(required.values[index]))
146
+ if (name.kind != PULSE_VALUE_STRING) return false
147
+ const claimIndex = __pulse_fastly_find(claims, name.text)
148
+ if (claimIndex < 0 || __pulse_fastly_value(unchecked(claims.values[claimIndex])).kind == PULSE_VALUE_UNDEFINED) return false
149
+ }
150
+ }
151
+
152
+ const issuerIndex = __pulse_fastly_find(policy, "issuer")
153
+ if (issuerIndex >= 0) {
154
+ const issuer = __pulse_jwt_string_member(claims, "iss")
155
+ if (issuer === null || !__pulse_jwt_config_accepts(unchecked(policy.values[issuerIndex]), issuer)) return false
156
+ }
157
+ if (!__pulse_jwt_audience_valid(claims, policy)) return false
158
+
159
+ const subjectIndex = __pulse_fastly_find(policy, "subject")
160
+ if (subjectIndex >= 0) {
161
+ const subject = __pulse_jwt_string_member(claims, "sub")
162
+ const expected = __pulse_fastly_value(unchecked(policy.values[subjectIndex]))
163
+ if (subject === null || expected.kind != PULSE_VALUE_STRING || subject != expected.text) return false
164
+ }
165
+
166
+ const toleranceIndex = __pulse_fastly_find(policy, "clockToleranceSeconds")
167
+ const tolerance = toleranceIndex < 0 ? 0.0 : __pulse_fastly_number(unchecked(policy.values[toleranceIndex]))
168
+ const expIndex = __pulse_fastly_find(claims, "exp")
169
+ if (expIndex >= 0) {
170
+ const exp = __pulse_jwt_numeric_claim(claims, "exp")
171
+ if (isNaN(exp) || now - tolerance >= exp) return false
172
+ }
173
+ const nbfIndex = __pulse_fastly_find(claims, "nbf")
174
+ if (nbfIndex >= 0) {
175
+ const nbf = __pulse_jwt_numeric_claim(claims, "nbf")
176
+ if (isNaN(nbf) || now + tolerance < nbf) return false
177
+ }
178
+ const iatIndex = __pulse_fastly_find(claims, "iat")
179
+ if (iatIndex >= 0 && isNaN(__pulse_jwt_numeric_claim(claims, "iat"))) return false
180
+ const maxAgeIndex = __pulse_fastly_find(policy, "maxTokenAgeSeconds")
181
+ if (maxAgeIndex >= 0) {
182
+ const iat = __pulse_jwt_numeric_claim(claims, "iat")
183
+ const maxAge = __pulse_fastly_number(unchecked(policy.values[maxAgeIndex]))
184
+ if (isNaN(iat) || iat > now + tolerance || now - iat - tolerance > maxAge) return false
185
+ }
186
+ return true
187
+ }
188
+
189
+ function __pulse_jwt_bearer(authorization: string): string | null {
190
+ if (authorization.length < 8 || authorization.indexOf("\r") >= 0 || authorization.indexOf("\n") >= 0 || authorization.indexOf(",") >= 0) return null
191
+ if (authorization.substring(0, 7).toLowerCase() != "bearer ") return null
192
+ const token = authorization.substring(7)
193
+ return token.length == 0 ? null : token
194
+ }
195
+
196
+ function __pulse_jwt_hex(bytes: Uint8Array): string {
197
+ const alphabet = "0123456789abcdef"
198
+ let output = ""
199
+ for (let index: i32 = 0; index < bytes.byteLength; index += 1) {
200
+ const value = unchecked(bytes[index])
201
+ output += alphabet.charAt(value >> 4) + alphabet.charAt(value & 15)
202
+ }
203
+ return output
204
+ }
205
+
206
+ function __pulse_jwt_es256_scalar_valid(
207
+ signature: Uint8Array,
208
+ offset: i32,
209
+ ): bool {
210
+ let nonzero = false
211
+ let lessThanOrder = false
212
+ let greaterThanOrder = false
213
+ for (let index: i32 = 0; index < 32; index += 1) {
214
+ const value: i32 = unchecked(signature[offset + index])
215
+ if (value != 0) nonzero = true
216
+ const order = unchecked(__PULSE_JWT_P256_ORDER[index])
217
+ if (!lessThanOrder && !greaterThanOrder) {
218
+ if (value < order) lessThanOrder = true
219
+ else if (value > order) greaterThanOrder = true
220
+ }
221
+ }
222
+ return nonzero && lessThanOrder && !greaterThanOrder
223
+ }
224
+
225
+ @lazy const __PULSE_JWT_P256_ORDER = [
226
+ 255, 255, 255, 255, 0, 0, 0, 0,
227
+ 255, 255, 255, 255, 255, 255, 255, 255,
228
+ 188, 230, 250, 173, 167, 23, 158, 132,
229
+ 243, 185, 202, 194, 252, 99, 37, 81,
230
+ ]
231
+
232
+ /**
233
+ * Provider-neutral package effect sentinel retained for canonical lowering.
234
+ */
235
+ export function pulse_jwt_verify(
236
+ operationRef: i32,
237
+ invocationRef: i32,
238
+ resultRef: i32,
239
+ ): i32 {
240
+ void operationRef
241
+ void invocationRef
242
+ void resultRef
243
+ return PULSE_JWT_VERIFY_CRYPTO_COMPOSITION_REQUIRED
244
+ }
245
+
246
+ /**
247
+ * Package-owned Fastly integration entry used only after canonical lowering.
248
+ * The provider supplies authority callbacks; this function supplies semantics.
249
+ */
250
+ export function pulse_jwt_fastly_verify(effectIndex: i32, outerHandle: i32): i32 {
251
+ const outer = __pulse_fastly_value(outerHandle)
252
+ if (outer.kind != PULSE_VALUE_OBJECT) return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_OPERATION_FAILED, 200)
253
+ const policy = __pulse_fastly_value(__pulse_jwt_object_field(outer, "payload"))
254
+ const invocation = __pulse_fastly_value(__pulse_jwt_object_field(outer, "invocation"))
255
+ if (policy.kind != PULSE_VALUE_OBJECT || invocation.kind != PULSE_VALUE_OBJECT) {
256
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_OPERATION_FAILED, 201)
257
+ }
258
+ const authorizationValue = __pulse_fastly_value(__pulse_jwt_object_field(invocation, "token"))
259
+ if (authorizationValue.kind == PULSE_VALUE_UNDEFINED) {
260
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_TOKEN_REQUIRED, 202)
261
+ }
262
+ if (authorizationValue.kind != PULSE_VALUE_STRING) {
263
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_BEARER_INVALID, 203)
264
+ }
265
+ const bearer = __pulse_jwt_bearer(authorizationValue.text)
266
+ if (bearer === null) return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_BEARER_INVALID, 204)
267
+ const token = bearer
268
+ if (String.UTF8.encode(token, false).byteLength > __PULSE_JWT_TOKEN_BYTES_MAX) {
269
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_LIMIT_EXCEEDED, 205)
270
+ }
271
+ const segments = token.split(".")
272
+ if (segments.length != 3) return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_MALFORMED, 206)
273
+ const protectedBytes = __pulse_jwt_base64url_decode(unchecked(segments[0]))
274
+ const claimsBytes = __pulse_jwt_base64url_decode(unchecked(segments[1]))
275
+ const signature = __pulse_jwt_base64url_decode(unchecked(segments[2]))
276
+ if (protectedBytes === null || claimsBytes === null || signature === null) {
277
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_MALFORMED, 207)
278
+ }
279
+ if (protectedBytes.byteLength > __PULSE_JWT_HEADER_BYTES_MAX || claimsBytes.byteLength > __PULSE_JWT_CLAIMS_BYTES_MAX) {
280
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_LIMIT_EXCEEDED, 208)
281
+ }
282
+ const protectedParser = new __PulseJsonParser(__pulse_jwt_decode_text(protectedBytes))
283
+ const protectedHandle = protectedParser.parse()
284
+ const protectedHeader = __pulse_fastly_value(protectedHandle)
285
+ if (protectedParser.failed || protectedHandle <= 0 || protectedHeader.kind != PULSE_VALUE_OBJECT) {
286
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_MALFORMED, 210)
287
+ }
288
+ if (__pulse_jwt_has_field(protectedHeader, "crit") || __pulse_jwt_has_field(protectedHeader, "b64")) {
289
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_MALFORMED, 211)
290
+ }
291
+ const algorithm = __pulse_jwt_string_member(protectedHeader, "alg")
292
+ const algorithmPolicy = __pulse_fastly_value(__pulse_jwt_object_field(policy, "algorithms"))
293
+ if (
294
+ algorithm === null ||
295
+ (algorithm != "HS256" && algorithm != "ES256") ||
296
+ !__pulse_jwt_array_contains(algorithmPolicy, algorithm)
297
+ ) {
298
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_ALGORITHM_NOT_ALLOWED, 212)
299
+ }
300
+ if (
301
+ (algorithm == "HS256" && signature.byteLength != __PULSE_JWT_HS256_SIGNATURE_BYTES) ||
302
+ (
303
+ algorithm == "ES256" &&
304
+ (
305
+ signature.byteLength != __PULSE_JWT_ES256_SIGNATURE_BYTES ||
306
+ !__pulse_jwt_es256_scalar_valid(signature, 0) ||
307
+ !__pulse_jwt_es256_scalar_valid(signature, 32)
308
+ )
309
+ )
310
+ ) {
311
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_MALFORMED, 209)
312
+ }
313
+ const typPolicy = __pulse_jwt_string_member(policy, "typ")
314
+ if (typPolicy !== null) {
315
+ const typ = __pulse_jwt_string_member(protectedHeader, "typ")
316
+ if (typ === null || typ != typPolicy) return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_CLAIMS_INVALID, 213)
317
+ }
318
+ const kidIndex = __pulse_fastly_find(protectedHeader, "kid")
319
+ if (kidIndex >= 0 && __pulse_fastly_value(unchecked(protectedHeader.values[kidIndex])).kind != PULSE_VALUE_STRING) {
320
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_MALFORMED, 214)
321
+ }
322
+ if (!__pulse_fastly_jwt_key_descriptor_valid(effectIndex)) {
323
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_KEY_INVALID, 215)
324
+ }
325
+
326
+ let key: Uint8Array | null
327
+ if (algorithm == "HS256") {
328
+ key = __pulse_fastly_jwt_resolve_secret(effectIndex)
329
+ if (key.byteLength < __PULSE_JWT_KEY_BYTES_MIN || key.byteLength > __PULSE_JWT_KEY_BYTES_MAX) {
330
+ __pulse_jwt_wipe(key)
331
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_KEY_INVALID, 216)
332
+ }
333
+ } else {
334
+ const kid = __pulse_jwt_string_member(protectedHeader, "kid")
335
+ key = __pulse_fastly_jwt_resolve_es256_key(effectIndex, kid)
336
+ if (key === null || key.byteLength != __PULSE_JWT_ES256_PUBLIC_KEY_BYTES) {
337
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_KEY_INVALID, 216)
338
+ }
339
+ }
340
+ const signingInput = unchecked(segments[0]) + "." + unchecked(segments[1])
341
+ const signingBytes = String.UTF8.encode(signingInput, false)
342
+ const signingView = Uint8Array.wrap(signingBytes)
343
+ __pulse_fastly_jwt_capture_signing_input(signingView)
344
+ const cryptoStatus = __pulse_fastly_jwt_force_crypto_failure(effectIndex)
345
+ ? -3
346
+ : __pulse_fastly_jwt_crypto_verify(
347
+ algorithm,
348
+ key,
349
+ signingView,
350
+ signature,
351
+ )
352
+ __pulse_fastly_jwt_record_crypto_attempt()
353
+ __pulse_jwt_wipe(key)
354
+ if (cryptoStatus == 0) return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_SIGNATURE_INVALID, 217)
355
+ if (cryptoStatus == -1) return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_KEY_INVALID, 218)
356
+ if (cryptoStatus != 1) return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_OPERATION_FAILED, 219)
357
+
358
+ const claimsParser = new __PulseJsonParser(__pulse_jwt_decode_text(claimsBytes))
359
+ const claimsHandle = claimsParser.parse()
360
+ const claims = __pulse_fastly_value(claimsHandle)
361
+ if (claimsParser.failed || claimsHandle <= 0 || claims.kind != PULSE_VALUE_OBJECT) {
362
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_MALFORMED, 220)
363
+ }
364
+ __pulse_fastly_jwt_record_claims_parsed()
365
+ const now = __pulse_fastly_jwt_capture_clock(effectIndex)
366
+ if (isNaN(now) || !isFinite(now)) return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_CLOCK_INVALID, 221)
367
+ if (!__pulse_jwt_validate_registered(claims, policy, now)) {
368
+ return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_CLAIMS_INVALID, 222)
369
+ }
370
+
371
+ let normalizedClaims = claimsHandle
372
+ const schema = __pulse_jwt_string_member(policy, "claimsSchema")
373
+ if (schema !== null) {
374
+ normalizedClaims = __pulse_fastly_jwt_validate_schema(effectIndex, schema, claimsHandle)
375
+ if (normalizedClaims <= 0) return __pulse_jwt_fail(effectIndex, __PULSE_JWT_ERROR_CLAIMS_SCHEMA_INVALID, 223)
376
+ }
377
+ const outputHeader = host_value_object()
378
+ host_value_object_set(outputHeader, __pulse_fastly_string_value("alg"), __pulse_fastly_string_value(algorithm))
379
+ if (kidIndex >= 0) host_value_object_set(outputHeader, __pulse_fastly_string_value("kid"), unchecked(protectedHeader.values[kidIndex]))
380
+ const typIndex = __pulse_fastly_find(protectedHeader, "typ")
381
+ if (typIndex >= 0) host_value_object_set(outputHeader, __pulse_fastly_string_value("typ"), unchecked(protectedHeader.values[typIndex]))
382
+ const output = host_value_object()
383
+ host_value_object_set(output, __pulse_fastly_string_value("claims"), normalizedClaims)
384
+ host_value_object_set(output, __pulse_fastly_string_value("protectedHeader"), outputHeader)
385
+ __pulse_fastly_deep_freeze(output)
386
+ __pulse_fastly_jwt_record_verified()
387
+ return output
388
+ }
@@ -0,0 +1,53 @@
1
+ {
2
+ "version": "pulse.jwt-native-contract-conformance.v1",
3
+ "status": "crypto-composed-hs256",
4
+ "operation": "jwt.verify",
5
+ "cases": [
6
+ "bearer-request-header",
7
+ "literal-verification-policy",
8
+ "runtime-secret-binding",
9
+ "claims-schema-reference",
10
+ "target-capability-selection",
11
+ "guest-source-pulse-hmac-as",
12
+ "exact-signing-and-tag-bytes",
13
+ "signature-before-claims",
14
+ "critical-header-rejection",
15
+ "non-finite-json-rejection",
16
+ "exact-time-boundary-parity",
17
+ "hostile-accessor-rejection",
18
+ "prototype-safe-result-detachment",
19
+ "no-key-material-artifact",
20
+ "no-guest-unit-for-hs256",
21
+ "dynamic-bearer-invocation",
22
+ "migrated-handler-javascript-native-parity",
23
+ "direct-and-parallel-parity",
24
+ "audience-and-time-boundary-parity",
25
+ "schema-and-resource-limit-parity",
26
+ "fastly-native-planning-rejection",
27
+ "redacted-rejection"
28
+ ],
29
+ "realizations": {
30
+ "crypto-composed": {
31
+ "status": "implemented",
32
+ "provider": "node",
33
+ "realization": "guest-source:pulse-hmac-as",
34
+ "implementation": "pulse-hmac-as.v1",
35
+ "algorithms": [
36
+ "HS256"
37
+ ],
38
+ "keyTypes": [
39
+ "secret"
40
+ ],
41
+ "guestUnitRequired": false,
42
+ "automaticFallback": false
43
+ }
44
+ },
45
+ "policy": {
46
+ "automaticFallback": false,
47
+ "nativeCryptoImplemented": true,
48
+ "secretMaterialExcludedFromCanonicalPlan": true,
49
+ "tokenExcludedFromStaticPlan": true,
50
+ "targetBindingRequired": true,
51
+ "cryptoSemanticOwner": "@pulse-compute/crypto"
52
+ }
53
+ }
@@ -0,0 +1,4 @@
1
+ import type { PulseRequest } from '@pulse-compute/runtime';
2
+ export declare function bearerFromAuthorizationValue(value: string | undefined): string | undefined;
3
+ export declare function bearer(request: PulseRequest | Request): string | undefined;
4
+ //# sourceMappingURL=bearer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer.d.ts","sourceRoot":"","sources":["../src/bearer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAC;AAsB3D,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAS1F;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAE1E"}
package/dist/bearer.js ADDED
@@ -0,0 +1,33 @@
1
+ import { jwtError } from './errors.js';
2
+ import { jwtOwnDataProperty } from './internal/data.js';
3
+ function authorizationValue(request) {
4
+ if (!request || typeof request !== 'object') {
5
+ throw jwtError('PULSE_JWT_BEARER_INVALID', { category: 'request' });
6
+ }
7
+ if (typeof Request !== 'undefined' && request instanceof Request) {
8
+ return request.headers.get('authorization') ?? undefined;
9
+ }
10
+ const header = jwtOwnDataProperty(request, 'header');
11
+ if (header.valid && typeof header.value === 'function') {
12
+ const value = header.value('authorization');
13
+ if (value === undefined || typeof value === 'string')
14
+ return value;
15
+ throw jwtError('PULSE_JWT_BEARER_INVALID', { category: 'authorization' });
16
+ }
17
+ throw jwtError('PULSE_JWT_BEARER_INVALID', { category: 'request' });
18
+ }
19
+ export function bearerFromAuthorizationValue(value) {
20
+ if (value === undefined)
21
+ return undefined;
22
+ if (value.length === 0 || /[\r\n,]/.test(value)) {
23
+ throw jwtError('PULSE_JWT_BEARER_INVALID', { category: 'authorization' });
24
+ }
25
+ const match = /^[Bb][Ee][Aa][Rr][Ee][Rr] ([A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+)$/.exec(value);
26
+ if (!match)
27
+ throw jwtError('PULSE_JWT_BEARER_INVALID', { category: 'authorization' });
28
+ return match[1];
29
+ }
30
+ export function bearer(request) {
31
+ return bearerFromAuthorizationValue(authorizationValue(request));
32
+ }
33
+ //# sourceMappingURL=bearer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bearer.js","sourceRoot":"","sources":["../src/bearer.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAExD,SAAS,kBAAkB,CAAC,OAA+B;IACzD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,MAAM,QAAQ,CAAC,0BAA0B,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;IACtE,CAAC;IAED,IAAI,OAAO,OAAO,KAAK,WAAW,IAAI,OAAO,YAAY,OAAO,EAAE,CAAC;QACjE,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,SAAS,CAAC;IAC3D,CAAC;IAED,MAAM,MAAM,GAAG,kBAAkB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACrD,IAAI,MAAM,CAAC,KAAK,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,UAAU,EAAE,CAAC;QACvD,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QAC5C,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,OAAO,KAAK,CAAC;QACnE,MAAM,QAAQ,CAAC,0BAA0B,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC;IAC5E,CAAC;IACD,MAAM,QAAQ,CAAC,0BAA0B,EAAE,EAAE,QAAQ,EAAE,SAAS,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,4BAA4B,CAAC,KAAyB;IACpE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAChD,MAAM,QAAQ,CAAC,0BAA0B,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,MAAM,KAAK,GAAG,6EAA6E,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACxG,IAAI,CAAC,KAAK;QAAE,MAAM,QAAQ,CAAC,0BAA0B,EAAE,EAAE,QAAQ,EAAE,eAAe,EAAE,CAAC,CAAC;IACtF,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,OAA+B;IACpD,OAAO,4BAA4B,CAAC,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;AACnE,CAAC"}
@@ -0,0 +1,19 @@
1
+ import { type CryptoVerificationResult, type MacVerifyRequest, type SignatureVerifyRequest } from '@pulse-compute/crypto';
2
+ import { type JwtClaims } from './options.js';
3
+ import { type JwtVerification } from './result.js';
4
+ import { type JwtVerifierHost, type JwtVerifierInput } from './internal/verifier-authority.js';
5
+ export type JwtCryptoVerifierInput = JwtVerifierInput;
6
+ export interface JwtCryptoVerifierHost extends JwtVerifierHost {
7
+ }
8
+ export type JwtCryptoMacVerify = (request: MacVerifyRequest) => CryptoVerificationResult | PromiseLike<CryptoVerificationResult>;
9
+ export type JwtCryptoSignatureVerify = (request: SignatureVerifyRequest) => CryptoVerificationResult | PromiseLike<CryptoVerificationResult>;
10
+ export interface JwtCryptoVerifier {
11
+ readonly mac: Readonly<{
12
+ readonly verify: JwtCryptoMacVerify;
13
+ }>;
14
+ readonly signature?: Readonly<{
15
+ readonly verify: JwtCryptoSignatureVerify;
16
+ }>;
17
+ }
18
+ export declare function verifyJwtWithCrypto<Claims = JwtClaims>(input: JwtCryptoVerifierInput, host: JwtCryptoVerifierHost, crypto: JwtCryptoVerifier): Promise<JwtVerification<Claims>>;
19
+ //# sourceMappingURL=crypto-verifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crypto-verifier.d.ts","sourceRoot":"","sources":["../src/crypto-verifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,wBAAwB,EAE7B,KAAK,gBAAgB,EACrB,KAAK,sBAAsB,EAC5B,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAIL,KAAK,SAAS,EAEf,MAAM,cAAc,CAAC;AACtB,OAAO,EAA4B,KAAK,eAAe,EAAE,MAAM,aAAa,CAAC;AAiB7E,OAAO,EAKL,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,kCAAkC,CAAC;AAE1C,MAAM,MAAM,sBAAsB,GAAG,gBAAgB,CAAC;AAEtD,MAAM,WAAW,qBAAsB,SAAQ,eAAe;CAAG;AAEjE,MAAM,MAAM,kBAAkB,GAAG,CAC/B,OAAO,EAAE,gBAAgB,KACtB,wBAAwB,GAAG,WAAW,CAAC,wBAAwB,CAAC,CAAC;AAEtE,MAAM,MAAM,wBAAwB,GAAG,CACrC,OAAO,EAAE,sBAAsB,KAC5B,wBAAwB,GAAG,WAAW,CAAC,wBAAwB,CAAC,CAAC;AAEtE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAC;QACrB,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;KACrC,CAAC,CAAC;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC;QAC5B,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC;KAC3C,CAAC,CAAC;CACJ;AA8HD,wBAAsB,mBAAmB,CAAC,MAAM,GAAG,SAAS,EAC1D,KAAK,EAAE,sBAAsB,EAC7B,IAAI,EAAE,qBAAqB,EAC3B,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,eAAe,CAAC,MAAM,CAAC,CAAC,CA2GlC"}