@tula-tech/contract 0.0.0-stage → 0.1.0-alpha.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 +202 -0
- package/README.md +38 -2
- package/dist/custom-claims.d.ts +161 -0
- package/dist/custom-claims.js +28 -0
- package/dist/device-binding.d.ts +217 -0
- package/dist/device-binding.js +24 -0
- package/dist/error-codes.d.ts +386 -0
- package/dist/error-codes.js +10 -0
- package/dist/event-types.d.ts +107 -0
- package/dist/event-types.js +10 -0
- package/dist/headers.d.ts +223 -0
- package/dist/headers.js +36 -0
- package/dist/index.d.ts +10384 -0
- package/dist/index.js +3912 -0
- package/dist/issuer.d.ts +31 -0
- package/dist/issuer.js +8 -0
- package/dist/password-rules.d.ts +452 -0
- package/dist/password-rules.js +10 -0
- package/dist/redirect-url.d.ts +223 -0
- package/dist/redirect-url.js +26 -0
- package/dist/shared/chunk-2xak18qy.js +56 -0
- package/dist/shared/chunk-4rb5jsfz.js +102 -0
- package/dist/shared/chunk-4stpmame.js +58 -0
- package/dist/shared/chunk-7yqzghgm.js +108 -0
- package/dist/shared/chunk-9pvdavrw.js +9 -0
- package/dist/shared/chunk-cce3bhet.js +251 -0
- package/dist/shared/chunk-pcn6ax54.js +195 -0
- package/dist/shared/chunk-q309e9bk.js +19 -0
- package/dist/shared/chunk-r202nxfw.js +438 -0
- package/dist/shared/chunk-sc6qg3d2.js +219 -0
- package/dist/theme.d.ts +227 -0
- package/dist/theme.js +16 -0
- package/dist/webhook-signature.d.ts +103 -0
- package/dist/webhook-signature.js +22 -0
- package/openapi.json +30076 -0
- package/package.json +79 -4
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,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @tula-tech/contract
|
|
2
2
|
|
|
3
|
-
|
|
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@alpha # or: npm install @tula-tech/contract@alpha
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> **Alpha.** Released under the `alpha` dist-tag: the protocol may change from one alpha to
|
|
14
|
+
> the next, and every `@tula-tech/*` package of a release shares one version, so install them at
|
|
15
|
+
> the same one. Everything else is in the
|
|
16
|
+
> [repository](https://github.com/Tula-Solutions/tula-auth-and-user-management#readme).
|
|
17
|
+
|
|
18
|
+
Most applications never import it: `@tula-tech/react`, `@tula-tech/nextjs` and `@tula-tech/core` are what an
|
|
19
|
+
app is built on, and they bring it. Import it yourself to check a payload against the
|
|
20
|
+
protocol, or for a constant an SDK does not re-export:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import { ERROR_CODES } from '@tula-tech/contract/error-codes'
|
|
24
|
+
import { FlowStepSchema } from '@tula-tech/contract'
|
|
25
|
+
|
|
26
|
+
const step = FlowStepSchema.parse(await response.json())
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Entry points
|
|
30
|
+
|
|
31
|
+
| Import | What |
|
|
32
|
+
| --- | --- |
|
|
33
|
+
| `@tula-tech/contract` | Everything, the Zod schemas included. It depends on Zod 4. |
|
|
34
|
+
| `@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. |
|
|
35
|
+
| `@tula-tech/contract/openapi.json` | The OpenAPI document of the API this version describes. |
|
|
36
|
+
|
|
37
|
+
The reference of every export is [docs/reference/contract.md](../../docs/reference/contract.md).
|
|
38
|
+
Under a strict Content-Security-Policy (no `'unsafe-eval'`), see
|
|
39
|
+
[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
|
+
};
|