@tula-tech/contract 0.0.0-stage → 0.1.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 ADDED
@@ -0,0 +1,202 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
202
+
package/README.md CHANGED
@@ -1,3 +1,38 @@
1
- # Temporary Holding Version
1
+ # @tula-tech/contract
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The public contract of [Tula Auth](../../README.md): the Zod schemas of every request and
4
+ response, the flow protocol (the steps a server answers a sign-up, a sign-in or a reset with),
5
+ the error codes, the token claims, the password policy, and the OpenAPI document they are
6
+ published as. The API validates with it and every Tula SDK is generated from it or tested
7
+ against it, so it is the one place the protocol is written down.
8
+
9
+ ```sh
10
+ bun add @tula-tech/contract@latest # or: npm install @tula-tech/contract@latest
11
+ ```
12
+
13
+ > **Early release (0.x).** The protocol may change from one release to the next, and every `@tula-tech/*` package of a release shares one version, so install them at
14
+ > the same one. Everything else is in the
15
+ > [repository](https://github.com/Tula-Solutions/tula-auth-and-user-management#readme).
16
+
17
+ Most applications never import it: `@tula-tech/react`, `@tula-tech/nextjs` and `@tula-tech/core` are what an
18
+ app is built on, and they bring it. Import it yourself to check a payload against the
19
+ protocol, or for a constant an SDK does not re-export:
20
+
21
+ ```ts
22
+ import { ERROR_CODES } from '@tula-tech/contract/error-codes'
23
+ import { FlowStepSchema } from '@tula-tech/contract'
24
+
25
+ const step = FlowStepSchema.parse(await response.json())
26
+ ```
27
+
28
+ ## Entry points
29
+
30
+ | Import | What |
31
+ | --- | --- |
32
+ | `@tula-tech/contract` | Everything, the Zod schemas included. It depends on Zod 4. |
33
+ | `@tula-tech/contract/error-codes`, `/event-types`, `/headers`, `/issuer`, `/password-rules`, `/redirect-url`, `/theme`, `/webhook-signature`, `/custom-claims`, `/device-binding` | The constants and pure functions an SDK needs at run time. None of them imports Zod, so none pulls it into an application's bundle. |
34
+ | `@tula-tech/contract/openapi.json` | The OpenAPI document of the API this version describes. |
35
+
36
+ The reference of every export is [docs/reference/contract.md](../../docs/reference/contract.md).
37
+ Under a strict Content-Security-Policy (no `'unsafe-eval'`), see
38
+ [docs/releasing.md](../../docs/releasing.md#tula-techcontract-under-a-strict-content-security-policy).
@@ -0,0 +1,161 @@
1
+ /**
2
+ * The one top-level claim every custom claim is nested under (ADR 0036).
3
+ *
4
+ * An operator's claims never sit beside Tula's own, so a claim Tula adds later cannot collide
5
+ * with a customer's, and a template cannot set a claim Tula's servers or SDKs act on. `ext`
6
+ * ("extension") is three bytes in every token, and is not a registered claim name (IANA's
7
+ * "JSON Web Token Claims" registry, read in October 2026) nor one OpenID Connect defines.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const role = payload[CUSTOM_CLAIMS_CLAIM]?.role // payload.ext.role
12
+ * ```
13
+ */
14
+ declare const CUSTOM_CLAIMS_CLAIM = "ext";
15
+ /**
16
+ * Claim names a custom claim's key may never be: every claim Tula's tokens carry, the
17
+ * registered claims a verifier acts on, `cnf` (reserved for device binding) and the namespace
18
+ * claim itself.
19
+ *
20
+ * A custom claim lives inside {@link CUSTOM_CLAIMS_CLAIM}, so it could not overwrite one of
21
+ * these anyway. They are refused so that nobody reads `claims.ext.sub` as the subject.
22
+ *
23
+ * @example
24
+ * ```ts
25
+ * RESERVED_CLAIM_NAMES.includes('sub') // true
26
+ * ```
27
+ */
28
+ declare const RESERVED_CLAIM_NAMES: readonly ["iss", "sub", "aud", "exp", "nbf", "iat", "jti", "sid", "pid", "eid", "v", "auth_time", "amr", "sp", "cnf", "ext"];
29
+ /**
30
+ * The most bytes the namespace claim's value may take as JSON (UTF-8), whatever puts claims
31
+ * in it.
32
+ *
33
+ * An access token is sent with every request and `@tula-tech/nextjs` keeps it in a cookie, where a
34
+ * browser allows about 4,096 bytes for the name and the value together. A token without
35
+ * custom claims is about 700 bytes; this cap adds at most 1,366 (base64url), which leaves
36
+ * room for a long issuer URL.
37
+ */
38
+ declare const MAX_CUSTOM_CLAIMS_BYTES = 1024;
39
+ /** Most JWT templates an environment may define. */
40
+ declare const MAX_JWT_TEMPLATES = 10;
41
+ /** Most claims one JWT template may define. */
42
+ declare const MAX_JWT_TEMPLATE_CLAIMS = 16;
43
+ /** Longest custom claim key, in characters. */
44
+ declare const MAX_CUSTOM_CLAIM_KEY_LENGTH = 32;
45
+ /** Longest string a template may set as a constant, in characters. */
46
+ declare const MAX_CUSTOM_CLAIM_CONSTANT_LENGTH = 256;
47
+ /** What a custom claim's value can be: one string, number or boolean. Never a list or an object. */
48
+ type CustomClaimValue = string | number | boolean;
49
+ /**
50
+ * The custom claims of a verified session, as an SDK hands them to an application: a frozen
51
+ * map from claim key to value.
52
+ *
53
+ * The values are typed `unknown` because only the operator knows what their template puts
54
+ * under each key: narrow before use. At run time each is a string, a number or a boolean.
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * const { customClaims } = await auth()
59
+ * const isAdmin = customClaims.role === 'admin' // a missing claim is `undefined`: "no"
60
+ * ```
61
+ */
62
+ type CustomClaims = Readonly<Record<string, unknown>>;
63
+ /**
64
+ * Whether a string can be the key of a custom claim: ASCII letters, digits and underscores,
65
+ * not starting with a digit, at most {@link MAX_CUSTOM_CLAIM_KEY_LENGTH} characters, and none
66
+ * of {@link RESERVED_CLAIM_NAMES}, `__proto__`, `constructor` or `prototype`.
67
+ *
68
+ * @param key - The candidate.
69
+ * @returns `true` when it can be a key.
70
+ *
71
+ * @example
72
+ * ```ts
73
+ * isCustomClaimKey('role') // true
74
+ * isCustomClaimKey('sub') // false: reserved
75
+ * isCustomClaimKey('my-claim') // false: a hyphen
76
+ * ```
77
+ */
78
+ declare function isCustomClaimKey(key: unknown): key is string;
79
+ /**
80
+ * Whether a value can be a custom claim's: a string, a boolean, or a number JSON can hold.
81
+ *
82
+ * @param value - The candidate.
83
+ * @returns `true` for a string, a boolean and a finite number.
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * isCustomClaimValue('admin') // true
88
+ * isCustomClaimValue(['admin']) // false
89
+ * ```
90
+ */
91
+ declare function isCustomClaimValue(value: unknown): value is CustomClaimValue;
92
+ /**
93
+ * How many bytes custom claims take in a token: the UTF-8 length of their JSON, which is what
94
+ * {@link MAX_CUSTOM_CLAIMS_BYTES} caps.
95
+ *
96
+ * @param claims - The value of the namespace claim.
97
+ * @returns The byte count.
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * customClaimsBytes({ role: 'admin' }) // 16: {"role":"admin"}
102
+ * ```
103
+ */
104
+ declare function customClaimsBytes(claims: Readonly<Record<string, CustomClaimValue>>): number;
105
+ /**
106
+ * What {@link checkCustomClaims} found: the claims, or why they are not claims.
107
+ *
108
+ * - `invalid`: not a plain object, a key that fails {@link isCustomClaimKey} (a reserved
109
+ * name and `__proto__` among them), or a value that is not one string, number or boolean.
110
+ * - `too_large`: every claim is fine and together they are over
111
+ * {@link MAX_CUSTOM_CLAIMS_BYTES}.
112
+ */
113
+ type CustomClaimsCheck = {
114
+ claims: Record<string, CustomClaimValue>;
115
+ } | {
116
+ problem: "invalid" | "too_large";
117
+ };
118
+ /**
119
+ * Judge claims that did not come from the environment's settings (a hook's answer), **whole**:
120
+ * either every one of them can be issued under the namespace claim, or none is.
121
+ *
122
+ * Nothing is repaired and nothing is left out: one bad key makes the whole value `invalid`.
123
+ * Give it the value as it was parsed (`JSON.parse`), not one a schema library has rebuilt:
124
+ * the keys are read as the object's **own** keys, so a `__proto__` key a parser kept is seen
125
+ * and refused, where a library that copies objects would have dropped it silently. An empty
126
+ * object is fine and means no claims.
127
+ *
128
+ * @param value - The candidate claims.
129
+ * @returns A copy of the claims (own keys only, no prototype reachable), or the problem.
130
+ *
131
+ * @example
132
+ * ```ts
133
+ * checkCustomClaims({ role: 'admin' }) // { claims: { role: 'admin' } }
134
+ * checkCustomClaims({ sub: 'someone-else' }) // { problem: 'invalid' }
135
+ * ```
136
+ */
137
+ declare function checkCustomClaims(value: unknown): CustomClaimsCheck;
138
+ /**
139
+ * Read the custom claims out of a session's claims, for an SDK to hand to an application.
140
+ *
141
+ * Call it only with claims that were **verified** (a token's signature and issuer, or the
142
+ * API's own answer): this checks a shape, not where the claims came from.
143
+ *
144
+ * The namespace claim is returned only when it is exactly what a Tula server issues: a plain
145
+ * object, not empty, at most {@link MAX_CUSTOM_CLAIMS_BYTES} as JSON, whose every key passes
146
+ * {@link isCustomClaimKey} and whose every value is a string, a boolean or a finite number.
147
+ * Anything else is **absent as a whole**, never passed through and never repaired: an
148
+ * application must not be handed an object of a shape it did not expect under a name it
149
+ * trusts. Treat an absent claim as "no".
150
+ *
151
+ * @param claims - A session's verified claims.
152
+ * @returns A frozen copy of the custom claims, or `null` when there are none.
153
+ *
154
+ * @example
155
+ * ```ts
156
+ * readCustomClaims({ sub: 'u1', ext: { role: 'admin' } }) // { role: 'admin' }
157
+ * readCustomClaims({ sub: 'u1', ext: ['admin'] }) // null
158
+ * ```
159
+ */
160
+ declare function readCustomClaims(claims: unknown): CustomClaims | null;
161
+ export { CUSTOM_CLAIMS_CLAIM, CustomClaimValue, CustomClaims, CustomClaimsCheck, MAX_CUSTOM_CLAIMS_BYTES, MAX_CUSTOM_CLAIM_CONSTANT_LENGTH, MAX_CUSTOM_CLAIM_KEY_LENGTH, MAX_JWT_TEMPLATES, MAX_JWT_TEMPLATE_CLAIMS, RESERVED_CLAIM_NAMES, checkCustomClaims, customClaimsBytes, isCustomClaimKey, isCustomClaimValue, readCustomClaims };
@@ -0,0 +1,28 @@
1
+ import {
2
+ CUSTOM_CLAIMS_CLAIM2,
3
+ RESERVED_CLAIM_NAMES2,
4
+ MAX_CUSTOM_CLAIMS_BYTES2,
5
+ MAX_JWT_TEMPLATES2,
6
+ MAX_JWT_TEMPLATE_CLAIMS2,
7
+ MAX_CUSTOM_CLAIM_KEY_LENGTH2,
8
+ MAX_CUSTOM_CLAIM_CONSTANT_LENGTH2,
9
+ isCustomClaimKey2,
10
+ isCustomClaimValue2,
11
+ customClaimsBytes2,
12
+ checkCustomClaims2,
13
+ readCustomClaims2
14
+ } from "./shared/chunk-4rb5jsfz.js";
15
+ export {
16
+ CUSTOM_CLAIMS_CLAIM2 as CUSTOM_CLAIMS_CLAIM,
17
+ MAX_CUSTOM_CLAIMS_BYTES2 as MAX_CUSTOM_CLAIMS_BYTES,
18
+ MAX_CUSTOM_CLAIM_CONSTANT_LENGTH2 as MAX_CUSTOM_CLAIM_CONSTANT_LENGTH,
19
+ MAX_CUSTOM_CLAIM_KEY_LENGTH2 as MAX_CUSTOM_CLAIM_KEY_LENGTH,
20
+ MAX_JWT_TEMPLATES2 as MAX_JWT_TEMPLATES,
21
+ MAX_JWT_TEMPLATE_CLAIMS2 as MAX_JWT_TEMPLATE_CLAIMS,
22
+ RESERVED_CLAIM_NAMES2 as RESERVED_CLAIM_NAMES,
23
+ checkCustomClaims2 as checkCustomClaims,
24
+ customClaimsBytes2 as customClaimsBytes,
25
+ isCustomClaimKey2 as isCustomClaimKey,
26
+ isCustomClaimValue2 as isCustomClaimValue,
27
+ readCustomClaims2 as readCustomClaims
28
+ };
@@ -0,0 +1,217 @@
1
+ /**
2
+ * Request header carrying the proof: one DPoP proof JWT (RFC 9449), made for this one request.
3
+ * Sent when an attempt starts, to bind the session it ends in, and on every refresh of a bound
4
+ * session.
5
+ *
6
+ * @example
7
+ * ```ts
8
+ * headers[DPOP_HEADER] = await createDpopProof(key, { method: 'POST', url, nonce })
9
+ * ```
10
+ */
11
+ declare const DPOP_HEADER = "DPoP";
12
+ /**
13
+ * Response header carrying the server's nonce: on the answer that asks for one
14
+ * (`device.nonce_required`), on every refresh of a bound session and on every start that
15
+ * brought a proof. A client keeps the newest one and puts it in its next proof.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * const nonce = response.headers.get(DPOP_NONCE_HEADER)
20
+ * ```
21
+ */
22
+ declare const DPOP_NONCE_HEADER = "DPoP-Nonce";
23
+ /**
24
+ * The `typ` of a proof's header. A token of any other type is not a proof.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * header.typ === DPOP_PROOF_TYPE // 'dpop+jwt'
29
+ * ```
30
+ */
31
+ declare const DPOP_PROOF_TYPE = "dpop+jwt";
32
+ /**
33
+ * The signature algorithms a proof may use: a closed list, and `ES256` alone. It is what the
34
+ * Secure Enclave and StrongBox sign with (ECDSA over P-256 with SHA-256); a later algorithm is
35
+ * added here on purpose, never accepted because a proof names it.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * DPOP_ALGORITHMS.includes(header.alg) // only 'ES256'
40
+ * ```
41
+ */
42
+ declare const DPOP_ALGORITHMS: readonly ["ES256"];
43
+ /** One of {@link DPOP_ALGORITHMS}. */
44
+ type DpopAlgorithm = (typeof DPOP_ALGORITHMS)[number];
45
+ /**
46
+ * Longest proof the server reads, in characters. A proof is a small header, six short claims
47
+ * and a 64-byte signature: about 500 characters. Anything longer is refused unread.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * proof.length <= MAX_DPOP_PROOF_LENGTH
52
+ * ```
53
+ */
54
+ declare const MAX_DPOP_PROOF_LENGTH = 2048;
55
+ /**
56
+ * The public half of a device key, as a proof's header carries it: a P-256 point and nothing
57
+ * else. No private member (`d`), no key id, no other field.
58
+ *
59
+ * @example
60
+ * ```ts
61
+ * const jwk: DevicePublicJwk = { kty: 'EC', crv: 'P-256', x: '…', y: '…' }
62
+ * ```
63
+ */
64
+ interface DevicePublicJwk {
65
+ kty: "EC";
66
+ crv: "P-256";
67
+ /** The point's x coordinate: 32 bytes, base64url without padding. */
68
+ x: string;
69
+ /** The point's y coordinate: 32 bytes, base64url without padding. */
70
+ y: string;
71
+ }
72
+ /**
73
+ * A key a session can be bound to: its public half, and a way to sign with the private half
74
+ * that never hands the private half out. A native SDK backs it with the Secure Enclave or
75
+ * StrongBox; {@link generateSoftwareDeviceKey} backs it with WebCrypto, for tests and for
76
+ * platforms with no hardware key.
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * const key: DeviceKey = await generateSoftwareDeviceKey()
81
+ * const proof = await createDpopProof(key, { method: 'POST', url, nonce })
82
+ * ```
83
+ */
84
+ interface DeviceKey {
85
+ /** The public key, sent in every proof's header. */
86
+ publicJwk: DevicePublicJwk;
87
+ /**
88
+ * Sign with ECDSA over P-256 and SHA-256.
89
+ *
90
+ * @param data - The bytes to sign (a proof's `header.payload`).
91
+ * @returns The signature in the JWS form: `r` then `s`, 32 bytes each (64 bytes). A platform
92
+ * that produces a DER signature converts it before returning.
93
+ */
94
+ sign(data: Uint8Array): Promise<Uint8Array>;
95
+ /**
96
+ * A unique id for one proof (its `jti`), for a runtime whose `crypto.getRandomValues`
97
+ * cannot be counted on (a native app’s JavaScript engine). Left out, 16 random bytes of
98
+ * the runtime’s own. The id is not a secret: the server only refuses one it has seen, so
99
+ * it has to be new every time and need not be unguessable.
100
+ *
101
+ * @returns An id no other proof of this key carries.
102
+ */
103
+ proofId?(): string;
104
+ }
105
+ /** What a proof is made for. */
106
+ interface DpopProofInput {
107
+ /** The request's method, e.g. `POST`: the proof's `htm`. */
108
+ method: string;
109
+ /**
110
+ * The request's address **as the API knows itself**: its public URL and the route's
111
+ * path. The proof's `htu`. Never the address of a proxy or of an app's own route handler
112
+ * in front of the API.
113
+ *
114
+ * It has one spelling: `http` or `https`, `://`, the host, an optional `:port` in digits,
115
+ * then a path that starts with `/`, in printable ASCII. A host may hold letters, digits,
116
+ * dots, hyphens and underscores, or be an IPv6 address in brackets. The server compares
117
+ * it, as text, with what a URL parser makes of its own public URL, so write it that way:
118
+ * the host as the server's public URL is written once lower-cased (letters outside ASCII
119
+ * in their `xn--` form, no dot added or removed at the end); an IPv6 host compressed and
120
+ * in brackets (`[::1]`); the port without leading zeros and left out when it is the
121
+ * scheme's default; the path exactly as the route's. The server forgives the case of the
122
+ * scheme and of the host and a default port written out (`:443`, `:80`), and nothing
123
+ * else. It **refuses** an address with a backslash, a `.` or `..` path segment, a percent
124
+ * sign, a query or a fragment (an empty one too), user info (`user@`), or a space, a tab
125
+ * or a line break. It is signed as given: this module does not rewrite it, so a client
126
+ * with a URL parser passes the API's URL through it first.
127
+ */
128
+ url: string;
129
+ /** The server's nonce, once the client has one ({@link DPOP_NONCE_HEADER}). */
130
+ nonce?: string;
131
+ /**
132
+ * The proof's unique id. Left out, the key's own `proofId()`, or 16 random bytes where the
133
+ * key has none. A proof is accepted once.
134
+ */
135
+ jti?: string;
136
+ /** When the proof is made, in milliseconds since the epoch. Left out, now. */
137
+ now?: number;
138
+ }
139
+ /**
140
+ * Whether a value is the public half of a device key and nothing more: exactly the four
141
+ * members of {@link DevicePublicJwk}, each coordinate 32 bytes in its one canonical
142
+ * base64url spelling (so that a key has one thumbprint). A key with a private member
143
+ * (`d`) or any other field is refused: a client that sends its private key has none.
144
+ *
145
+ * It does not check that the point is on the curve; importing the key does.
146
+ *
147
+ * @param value - What a proof's header holds under `jwk`.
148
+ * @returns `true` for a well-formed public P-256 key.
149
+ * @example
150
+ * ```ts
151
+ * isDevicePublicJwk({ kty: 'EC', crv: 'P-256', x, y }) // true
152
+ * isDevicePublicJwk({ kty: 'EC', crv: 'P-256', x, y, d }) // false
153
+ * ```
154
+ */
155
+ declare function isDevicePublicJwk(value: unknown): value is DevicePublicJwk;
156
+ /**
157
+ * Whether a value has the shape of a key thumbprint: 43 base64url characters (a SHA-256).
158
+ *
159
+ * @param value - A stored or received thumbprint.
160
+ * @returns `true` for a well-formed one.
161
+ * @example
162
+ * ```ts
163
+ * isKeyThumbprint(claims.cnf?.jkt)
164
+ * ```
165
+ */
166
+ declare function isKeyThumbprint(value: unknown): value is string;
167
+ /**
168
+ * The thumbprint of a device key (RFC 7638, SHA-256, base64url): what a bound session stores
169
+ * and what its access token carries as `cnf.jkt`. Two keys have the same thumbprint only when
170
+ * they are the same key.
171
+ *
172
+ * @param jwk - The public key.
173
+ * @returns 43 base64url characters.
174
+ * @example
175
+ * ```ts
176
+ * const jkt = await jwkThumbprint(key.publicJwk)
177
+ * ```
178
+ */
179
+ declare function jwkThumbprint(jwk: DevicePublicJwk): Promise<string>;
180
+ /**
181
+ * Make a proof for one request: a JWT of type `dpop+jwt`, signed by the device key, carrying
182
+ * the key's public half in its header and the request's method (`htm`), the API's address
183
+ * (`htu`), the time (`iat`), a unique id (`jti`) and the server's nonce.
184
+ *
185
+ * A proof is good for one request: make a new one every time, never keep or reuse one.
186
+ *
187
+ * @param key - The device key.
188
+ * @param input - The request the proof is for, and the server's nonce.
189
+ * @returns The compact JWT for the {@link DPOP_HEADER} header.
190
+ * @example
191
+ * ```ts
192
+ * const proof = await createDpopProof(key, {
193
+ * method: 'POST',
194
+ * url: 'https://auth.example.com/v1/client/sessions/refresh',
195
+ * nonce,
196
+ * })
197
+ * ```
198
+ */
199
+ declare function createDpopProof(key: DeviceKey, input: DpopProofInput): Promise<string>;
200
+ /**
201
+ * Make a device key in software, with WebCrypto: a fresh P-256 key pair whose private half is
202
+ * **not extractable**, so the page or process that holds it can sign with it and cannot read
203
+ * it. It lives in memory only; a client that wants it to outlive the process keeps the
204
+ * `CryptoKey` where its platform allows (IndexedDB stores one without exposing it).
205
+ *
206
+ * This is what tests and the conformance runner bind with. It proves nothing about hardware:
207
+ * a native SDK supplies its own {@link DeviceKey} backed by the Secure Enclave or StrongBox.
208
+ *
209
+ * @returns A key whose `sign` uses the private half.
210
+ * @example
211
+ * ```ts
212
+ * const deviceKey = await generateSoftwareDeviceKey()
213
+ * const tula = createTulaClient({ publishableKey, baseUrl, client: 'ios', deviceKey })
214
+ * ```
215
+ */
216
+ declare function generateSoftwareDeviceKey(): Promise<DeviceKey>;
217
+ export { DPOP_ALGORITHMS, DPOP_HEADER, DPOP_NONCE_HEADER, DPOP_PROOF_TYPE, DeviceKey, DevicePublicJwk, DpopAlgorithm, DpopProofInput, MAX_DPOP_PROOF_LENGTH, createDpopProof, generateSoftwareDeviceKey, isDevicePublicJwk, isKeyThumbprint, jwkThumbprint };
@@ -0,0 +1,24 @@
1
+ import {
2
+ DPOP_HEADER2,
3
+ DPOP_NONCE_HEADER2,
4
+ DPOP_PROOF_TYPE2,
5
+ DPOP_ALGORITHMS2,
6
+ MAX_DPOP_PROOF_LENGTH2,
7
+ isDevicePublicJwk2,
8
+ isKeyThumbprint2,
9
+ jwkThumbprint2,
10
+ createDpopProof2,
11
+ generateSoftwareDeviceKey2
12
+ } from "./shared/chunk-4stpmame.js";
13
+ export {
14
+ DPOP_ALGORITHMS2 as DPOP_ALGORITHMS,
15
+ DPOP_HEADER2 as DPOP_HEADER,
16
+ DPOP_NONCE_HEADER2 as DPOP_NONCE_HEADER,
17
+ DPOP_PROOF_TYPE2 as DPOP_PROOF_TYPE,
18
+ MAX_DPOP_PROOF_LENGTH2 as MAX_DPOP_PROOF_LENGTH,
19
+ createDpopProof2 as createDpopProof,
20
+ generateSoftwareDeviceKey2 as generateSoftwareDeviceKey,
21
+ isDevicePublicJwk2 as isDevicePublicJwk,
22
+ isKeyThumbprint2 as isKeyThumbprint,
23
+ jwkThumbprint2 as jwkThumbprint
24
+ };