@structured-id/ui-core 0.1.5

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 (156) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +47 -0
  3. package/dist/browser-vp/index.cjs +349 -0
  4. package/dist/browser-vp/index.d.cts +287 -0
  5. package/dist/browser-vp/index.d.ts +287 -0
  6. package/dist/browser-vp/index.js +349 -0
  7. package/dist/chunk-C3Q766CY.cjs +125 -0
  8. package/dist/chunk-J5YXRXB5.js +125 -0
  9. package/dist/index.cjs +19360 -0
  10. package/dist/index.d.cts +9536 -0
  11. package/dist/index.d.ts +9536 -0
  12. package/dist/index.js +19360 -0
  13. package/dist/storage-4CB2JTLW.cjs +22 -0
  14. package/dist/storage-THRWREZP.js +22 -0
  15. package/package.json +93 -0
  16. package/src/browser-vp/crypto.ts +254 -0
  17. package/src/browser-vp/index.test.ts +10 -0
  18. package/src/browser-vp/index.ts +118 -0
  19. package/src/browser-vp/prf.ts +181 -0
  20. package/src/browser-vp/storage.test.ts +9 -0
  21. package/src/browser-vp/storage.ts +165 -0
  22. package/src/browser-vp/types.ts +97 -0
  23. package/src/browser-vp/vp.test.ts +43 -0
  24. package/src/browser-vp/vp.ts +113 -0
  25. package/src/components/SidAvatar.vue +106 -0
  26. package/src/components/SidConfirmDialog.vue +125 -0
  27. package/src/components/SidStatusBadge.vue +71 -0
  28. package/src/components/index.ts +5 -0
  29. package/src/composables/index.ts +10 -0
  30. package/src/composables/useAuth.ts +40 -0
  31. package/src/composables/useBffAuth.ts +147 -0
  32. package/src/composables/useGrpc.test.ts +196 -0
  33. package/src/composables/useGrpc.ts +120 -0
  34. package/src/composables/useSession.ts +80 -0
  35. package/src/generated/google/api/http.ts +687 -0
  36. package/src/generated/google/protobuf/descriptor.ts +4865 -0
  37. package/src/generated/google/protobuf/duration.ts +230 -0
  38. package/src/generated/google/protobuf/empty.ts +94 -0
  39. package/src/generated/google/protobuf/struct.ts +484 -0
  40. package/src/generated/google/protobuf/timestamp.ts +287 -0
  41. package/src/generated/sid/v1/account/account.client.ts +527 -0
  42. package/src/generated/sid/v1/account/account.ts +3261 -0
  43. package/src/generated/sid/v1/admin/admin.client.ts +136 -0
  44. package/src/generated/sid/v1/admin/admin.ts +846 -0
  45. package/src/generated/sid/v1/admin/admin_roles.client.ts +181 -0
  46. package/src/generated/sid/v1/admin/admin_roles.ts +1606 -0
  47. package/src/generated/sid/v1/admin/analytics.client.ts +106 -0
  48. package/src/generated/sid/v1/admin/analytics.ts +1017 -0
  49. package/src/generated/sid/v1/admin/branding.client.ts +166 -0
  50. package/src/generated/sid/v1/admin/branding.ts +1906 -0
  51. package/src/generated/sid/v1/admin/enrollment.client.ts +165 -0
  52. package/src/generated/sid/v1/admin/enrollment.ts +1704 -0
  53. package/src/generated/sid/v1/admin/events.client.ts +195 -0
  54. package/src/generated/sid/v1/admin/events.ts +1900 -0
  55. package/src/generated/sid/v1/admin/flow.client.ts +183 -0
  56. package/src/generated/sid/v1/admin/flow.ts +1552 -0
  57. package/src/generated/sid/v1/admin/keys.client.ts +107 -0
  58. package/src/generated/sid/v1/admin/keys.ts +931 -0
  59. package/src/generated/sid/v1/admin/login_experience.client.ts +160 -0
  60. package/src/generated/sid/v1/admin/login_experience.ts +1061 -0
  61. package/src/generated/sid/v1/admin/migration.client.ts +127 -0
  62. package/src/generated/sid/v1/admin/migration.ts +1948 -0
  63. package/src/generated/sid/v1/admin/notifications.client.ts +128 -0
  64. package/src/generated/sid/v1/admin/notifications.ts +1332 -0
  65. package/src/generated/sid/v1/admin/operations.client.ts +241 -0
  66. package/src/generated/sid/v1/admin/operations.ts +2648 -0
  67. package/src/generated/sid/v1/admin/organizations.client.ts +57 -0
  68. package/src/generated/sid/v1/admin/organizations.ts +349 -0
  69. package/src/generated/sid/v1/admin/scopes.client.ts +171 -0
  70. package/src/generated/sid/v1/admin/scopes.ts +1324 -0
  71. package/src/generated/sid/v1/admin/security.client.ts +412 -0
  72. package/src/generated/sid/v1/admin/security.ts +4086 -0
  73. package/src/generated/sid/v1/admin/sessions.client.ts +114 -0
  74. package/src/generated/sid/v1/admin/sessions.ts +923 -0
  75. package/src/generated/sid/v1/admin/settings.client.ts +340 -0
  76. package/src/generated/sid/v1/admin/settings.ts +2395 -0
  77. package/src/generated/sid/v1/admin/user_profile.client.ts +57 -0
  78. package/src/generated/sid/v1/admin/user_profile.ts +730 -0
  79. package/src/generated/sid/v1/admin/webhooks.client.ts +269 -0
  80. package/src/generated/sid/v1/admin/webhooks.ts +2804 -0
  81. package/src/generated/sid/v1/attestation/attestation.client.ts +115 -0
  82. package/src/generated/sid/v1/attestation/attestation.ts +813 -0
  83. package/src/generated/sid/v1/authn/auth.client.ts +900 -0
  84. package/src/generated/sid/v1/authn/auth.ts +9287 -0
  85. package/src/generated/sid/v1/authn/flows.client.ts +413 -0
  86. package/src/generated/sid/v1/authn/flows.ts +3217 -0
  87. package/src/generated/sid/v1/authz/authz.client.ts +459 -0
  88. package/src/generated/sid/v1/authz/authz.ts +5343 -0
  89. package/src/generated/sid/v1/authz/governance.client.ts +102 -0
  90. package/src/generated/sid/v1/authz/governance.ts +818 -0
  91. package/src/generated/sid/v1/common/errors.ts +184 -0
  92. package/src/generated/sid/v1/common/pagination.ts +152 -0
  93. package/src/generated/sid/v1/common/transport.ts +227 -0
  94. package/src/generated/sid/v1/common/types.ts +151 -0
  95. package/src/generated/sid/v1/events/event_stream.client.ts +67 -0
  96. package/src/generated/sid/v1/events/event_stream.ts +374 -0
  97. package/src/generated/sid/v1/federation/upstream.client.ts +142 -0
  98. package/src/generated/sid/v1/federation/upstream.ts +1736 -0
  99. package/src/generated/sid/v1/identity/identity.client.ts +467 -0
  100. package/src/generated/sid/v1/identity/identity.ts +5613 -0
  101. package/src/generated/sid/v1/machine/machine_user.client.ts +219 -0
  102. package/src/generated/sid/v1/machine/machine_user.ts +2400 -0
  103. package/src/generated/sid/v1/machine/pat.client.ts +147 -0
  104. package/src/generated/sid/v1/machine/pat.ts +1268 -0
  105. package/src/generated/sid/v1/projects/projects.client.ts +286 -0
  106. package/src/generated/sid/v1/projects/projects.ts +2714 -0
  107. package/src/generated/sid/v1/scim/scim.client.ts +274 -0
  108. package/src/generated/sid/v1/scim/scim.ts +3043 -0
  109. package/src/generated/sid/v1/test/test.client.ts +232 -0
  110. package/src/generated/sid/v1/test/test.ts +1136 -0
  111. package/src/i18n/index.ts +40 -0
  112. package/src/index.ts +157 -0
  113. package/src/profile/auth.ts +15 -0
  114. package/src/profile/components/ConsentManager.vue +88 -0
  115. package/src/profile/components/ProfileEditor.vue +146 -0
  116. package/src/profile/components/SecuritySettings.vue +170 -0
  117. package/src/profile/components/SessionList.vue +100 -0
  118. package/src/profile/components/SidLoginForm.test.ts +80 -0
  119. package/src/profile/components/SidLoginForm.vue +230 -0
  120. package/src/profile/components/SidMfaChallenge.test.ts +127 -0
  121. package/src/profile/components/SidMfaChallenge.vue +243 -0
  122. package/src/profile/components/SidRegistrationForm.test.ts +79 -0
  123. package/src/profile/components/SidRegistrationForm.vue +317 -0
  124. package/src/profile/components/index.ts +11 -0
  125. package/src/profile/composables/index.ts +6 -0
  126. package/src/profile/composables/useAccountApi.ts +203 -0
  127. package/src/profile/composables/useAuthApi.opaque.test.ts +140 -0
  128. package/src/profile/composables/useAuthApi.ts +617 -0
  129. package/src/profile/composables/useIdentityApi.ts +123 -0
  130. package/src/profile/composables/useLoginSuccess.test.ts +112 -0
  131. package/src/profile/composables/useLoginSuccess.ts +65 -0
  132. package/src/profile/composables/useOpaqueAuth.test.ts +290 -0
  133. package/src/profile/composables/useOpaqueAuth.ts +191 -0
  134. package/src/profile/composables/useSecurityLevel.test.ts +198 -0
  135. package/src/profile/composables/useSecurityLevel.ts +324 -0
  136. package/src/profile/index.ts +18 -0
  137. package/src/quasar/SidPrincipalInput.vue +209 -0
  138. package/src/quasar/index.ts +8 -0
  139. package/src/stores/auth.test.ts +83 -0
  140. package/src/stores/auth.ts +92 -0
  141. package/src/stores/index.ts +1 -0
  142. package/src/transport/adaptive.test.ts +728 -0
  143. package/src/transport/adaptive.ts +308 -0
  144. package/src/transport/framing.test.ts +227 -0
  145. package/src/transport/framing.ts +98 -0
  146. package/src/transport/webtransport.test.ts +728 -0
  147. package/src/transport/webtransport.ts +323 -0
  148. package/src/types/auth.ts +58 -0
  149. package/src/types/index.ts +1 -0
  150. package/src/utils/formatters.test.ts +26 -0
  151. package/src/utils/formatters.ts +69 -0
  152. package/src/utils/index.ts +16 -0
  153. package/src/utils/normalizePrincipal.test.ts +188 -0
  154. package/src/utils/normalizePrincipal.ts +159 -0
  155. package/src/utils/validators.test.ts +75 -0
  156. package/src/utils/validators.ts +36 -0
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # @structured-id/ui-core
2
+
3
+ Shared UI package for StructuredID applications. Provides Vue 3 composables, Pinia stores, protobuf-ts generated clients, and reusable components.
4
+
5
+ ## What's Inside
6
+
7
+ - **Proto clients** -- gRPC-Web clients generated from [proto](https://github.com/structured-id/proto) via protobuf-ts
8
+ - **Composables** -- `useAuth`, `useAuthApi`, `useAccountApi`, `useIdentityApi`, `useSecurityLevel`, `useOpaqueAuth`
9
+ - **Components** -- `SidLoginForm`, `SidRegistrationForm`, `SidMfaChallenge`, `SidPrincipalInput`, profile management pages
10
+ - **Utilities** -- `normalizePrincipal`, `detectLoginInputType`, validators, formatters
11
+
12
+ ## Usage
13
+
14
+ ```ts
15
+ import { useAuth, getTransport, initGrpc } from "@structured-id/ui-core";
16
+ import { SidLoginForm } from "@structured-id/ui-core/profile";
17
+
18
+ // Initialize gRPC transport
19
+ initGrpc({ baseUrl: "http://localhost:9080" });
20
+ ```
21
+
22
+ ## Development
23
+
24
+ ```bash
25
+ yarn install
26
+ yarn build # Build library
27
+ yarn test # Run tests (vitest)
28
+ yarn typecheck # vue-tsc --noEmit
29
+ ```
30
+
31
+ ## Architecture
32
+
33
+ ```
34
+ src/
35
+ composables/ # Vue 3 composition API (useAuth, useAuthApi)
36
+ stores/ # Pinia stores (auth)
37
+ profile/ # Profile components and composables
38
+ components/ # SidLoginForm, SidRegistrationForm, SidMfaChallenge
39
+ composables/ # useOpaqueAuth, useAccountApi, useIdentityApi
40
+ utils/ # normalizePrincipal, validators, formatters
41
+ generated/ # protobuf-ts generated clients (do not edit)
42
+ proto/ # Proto definitions submodule
43
+ ```
44
+
45
+ ## License
46
+
47
+ AGPL-3.0-only -- see [LICENSE](LICENSE) for details.
@@ -0,0 +1,349 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }
2
+
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+ var _chunkC3Q766CYcjs = require('../chunk-C3Q766CY.cjs');
12
+
13
+ // src/browser-vp/prf.ts
14
+ var PRF_EVAL_LABEL = "sid-ppk-v1";
15
+ var PRF_PROBE_LABEL = "sid-probe";
16
+ var encoder = new TextEncoder();
17
+ async function hasPrfSupport() {
18
+ try {
19
+ if (typeof window === "undefined" || !window.PublicKeyCredential) {
20
+ return false;
21
+ }
22
+ if (typeof PublicKeyCredential.getClientCapabilities === "function") {
23
+ try {
24
+ const caps = await PublicKeyCredential.getClientCapabilities();
25
+ if (caps && "prf" in caps) {
26
+ return Boolean(caps.prf);
27
+ }
28
+ } catch (e) {
29
+ }
30
+ }
31
+ const result = await navigator.credentials.get({
32
+ publicKey: {
33
+ challenge: crypto.getRandomValues(new Uint8Array(32)),
34
+ rpId: window.location.hostname,
35
+ userVerification: "discouraged",
36
+ extensions: {
37
+ prf: {
38
+ eval: {
39
+ first: encoder.encode(PRF_PROBE_LABEL)
40
+ }
41
+ }
42
+ }
43
+ }
44
+ });
45
+ if (!result) return false;
46
+ const ext = result.getClientExtensionResults();
47
+ const prfResults = _optionalChain([ext, 'optionalAccess', _ => _.prf]);
48
+ return _optionalChain([prfResults, 'optionalAccess', _2 => _2.results, 'optionalAccess', _3 => _3.first]) != null;
49
+ } catch (e2) {
50
+ return false;
51
+ }
52
+ }
53
+ async function evaluatePrf(credentialId) {
54
+ try {
55
+ const allowCredentials = credentialId ? [{ type: "public-key", id: credentialId }] : void 0;
56
+ const result = await navigator.credentials.get({
57
+ publicKey: {
58
+ challenge: crypto.getRandomValues(new Uint8Array(32)),
59
+ rpId: window.location.hostname,
60
+ userVerification: "required",
61
+ allowCredentials,
62
+ extensions: {
63
+ prf: {
64
+ eval: {
65
+ first: encoder.encode(PRF_EVAL_LABEL)
66
+ }
67
+ }
68
+ }
69
+ }
70
+ });
71
+ if (!result) return null;
72
+ const ext = result.getClientExtensionResults();
73
+ const prfResults = _optionalChain([ext, 'optionalAccess', _4 => _4.prf]);
74
+ return _nullishCoalesce(_optionalChain([prfResults, 'optionalAccess', _5 => _5.results, 'optionalAccess', _6 => _6.first]), () => ( null));
75
+ } catch (e3) {
76
+ return null;
77
+ }
78
+ }
79
+ async function createPasskeyWithPrf(userId, userName, rpName) {
80
+ try {
81
+ const result = await navigator.credentials.create({
82
+ publicKey: {
83
+ challenge: crypto.getRandomValues(new Uint8Array(32)),
84
+ rp: {
85
+ name: rpName,
86
+ id: window.location.hostname
87
+ },
88
+ user: {
89
+ id: userId,
90
+ name: userName,
91
+ displayName: userName
92
+ },
93
+ pubKeyCredParams: [
94
+ { alg: -7, type: "public-key" },
95
+ // ES256
96
+ { alg: -257, type: "public-key" }
97
+ // RS256 fallback
98
+ ],
99
+ authenticatorSelection: {
100
+ residentKey: "required",
101
+ userVerification: "required"
102
+ },
103
+ extensions: {
104
+ prf: {
105
+ eval: {
106
+ first: encoder.encode(PRF_EVAL_LABEL)
107
+ }
108
+ }
109
+ }
110
+ }
111
+ });
112
+ if (!result) return null;
113
+ const credential = result;
114
+ const ext = credential.getClientExtensionResults();
115
+ const prfResults = _optionalChain([ext, 'optionalAccess', _7 => _7.prf]);
116
+ const prfOutput = _optionalChain([prfResults, 'optionalAccess', _8 => _8.results, 'optionalAccess', _9 => _9.first]);
117
+ if (!prfOutput) return null;
118
+ return { credential, prfOutput };
119
+ } catch (e4) {
120
+ return null;
121
+ }
122
+ }
123
+
124
+ // src/browser-vp/crypto.ts
125
+ var PRF_SALT = "sid-ppk-v1";
126
+ var SIGNING_INFO = "signing-key";
127
+ var DATA_KEY_INFO = "data-encryption-key";
128
+ var encoder2 = new TextEncoder();
129
+ async function deriveKeysFromPrf(prfOutput) {
130
+ const baseKey = await crypto.subtle.importKey(
131
+ "raw",
132
+ prfOutput,
133
+ "HKDF",
134
+ false,
135
+ ["deriveBits"]
136
+ );
137
+ const salt = encoder2.encode(PRF_SALT);
138
+ const signingBits = await crypto.subtle.deriveBits(
139
+ { name: "HKDF", hash: "SHA-256", salt, info: encoder2.encode(SIGNING_INFO) },
140
+ baseKey,
141
+ 256
142
+ );
143
+ const dataKeyBits = await crypto.subtle.deriveBits(
144
+ {
145
+ name: "HKDF",
146
+ hash: "SHA-256",
147
+ salt,
148
+ info: encoder2.encode(DATA_KEY_INFO)
149
+ },
150
+ baseKey,
151
+ 256
152
+ );
153
+ const signingKey = await importEcdsaFromSeed(signingBits);
154
+ const dataKey = await crypto.subtle.importKey(
155
+ "raw",
156
+ dataKeyBits,
157
+ { name: "AES-GCM", length: 256 },
158
+ false,
159
+ ["encrypt", "decrypt"]
160
+ );
161
+ return { signingKey, dataKey };
162
+ }
163
+ async function generateDeviceKey() {
164
+ return crypto.subtle.generateKey(
165
+ { name: "ECDSA", namedCurve: "P-256" },
166
+ false,
167
+ // non-extractable
168
+ ["sign", "verify"]
169
+ );
170
+ }
171
+ async function generateDataKey() {
172
+ return crypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, false, [
173
+ "encrypt",
174
+ "decrypt"
175
+ ]);
176
+ }
177
+ async function wrapKey(keyToWrap, wrappingKey) {
178
+ return crypto.subtle.wrapKey("jwk", keyToWrap, wrappingKey, "AES-KW");
179
+ }
180
+ async function unwrapSigningKey(wrappedKey, wrappingKey) {
181
+ return crypto.subtle.unwrapKey(
182
+ "jwk",
183
+ wrappedKey,
184
+ wrappingKey,
185
+ "AES-KW",
186
+ { name: "ECDSA", namedCurve: "P-256" },
187
+ false,
188
+ // non-extractable
189
+ ["sign"]
190
+ );
191
+ }
192
+ async function deriveWrappingKey(sessionExportKey) {
193
+ const baseKey = await crypto.subtle.importKey(
194
+ "raw",
195
+ sessionExportKey,
196
+ "HKDF",
197
+ false,
198
+ ["deriveKey"]
199
+ );
200
+ return crypto.subtle.deriveKey(
201
+ {
202
+ name: "HKDF",
203
+ hash: "SHA-256",
204
+ salt: encoder2.encode("sid-device-key-wrap"),
205
+ info: encoder2.encode("wrapping-key")
206
+ },
207
+ baseKey,
208
+ { name: "AES-KW", length: 256 },
209
+ false,
210
+ ["wrapKey", "unwrapKey"]
211
+ );
212
+ }
213
+ async function encrypt(data, key) {
214
+ const iv = crypto.getRandomValues(new Uint8Array(12));
215
+ const ciphertext = await crypto.subtle.encrypt(
216
+ { name: "AES-GCM", iv },
217
+ key,
218
+ data
219
+ );
220
+ const result = new Uint8Array(iv.length + ciphertext.byteLength);
221
+ result.set(iv, 0);
222
+ result.set(new Uint8Array(ciphertext), iv.length);
223
+ return result.buffer;
224
+ }
225
+ async function decrypt(data, key) {
226
+ const bytes = new Uint8Array(data);
227
+ const iv = bytes.slice(0, 12);
228
+ const ciphertext = bytes.slice(12);
229
+ return crypto.subtle.decrypt({ name: "AES-GCM", iv }, key, ciphertext);
230
+ }
231
+ async function sign(data, privateKey) {
232
+ return crypto.subtle.sign(
233
+ { name: "ECDSA", hash: "SHA-256" },
234
+ privateKey,
235
+ data
236
+ );
237
+ }
238
+ async function exportPublicKey(keyPair) {
239
+ return crypto.subtle.exportKey("raw", keyPair.publicKey);
240
+ }
241
+ async function sha256(data) {
242
+ const hash = await crypto.subtle.digest("SHA-256", data);
243
+ return Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("");
244
+ }
245
+ async function importEcdsaFromSeed(_seed) {
246
+ return crypto.subtle.generateKey(
247
+ { name: "ECDSA", namedCurve: "P-256" },
248
+ false,
249
+ ["sign", "verify"]
250
+ );
251
+ }
252
+
253
+ // src/browser-vp/vp.ts
254
+ var encoder3 = new TextEncoder();
255
+ async function createVp(request, signingKey, profileBlob) {
256
+ const binding = await _chunkC3Q766CYcjs.loadBinding.call(void 0, request.serviceSector);
257
+ if (!binding) {
258
+ return null;
259
+ }
260
+ if (binding.certExpiry < /* @__PURE__ */ new Date()) {
261
+ return null;
262
+ }
263
+ const payload = buildPayload(request, binding, profileBlob);
264
+ const signature = await sign(payload, signingKey);
265
+ await _chunkC3Q766CYcjs.saveBinding.call(void 0, request.serviceSector, {
266
+ ...binding,
267
+ lastUsedAt: /* @__PURE__ */ new Date()
268
+ });
269
+ return {
270
+ serviceSector: request.serviceSector,
271
+ bindingId: binding.bindingId,
272
+ payload,
273
+ signature,
274
+ certChain: binding.certChain,
275
+ nonce: request.nonce,
276
+ audience: request.audience
277
+ };
278
+ }
279
+ function buildPayload(request, binding, _profileBlob) {
280
+ const payload = {
281
+ nonce: request.nonce,
282
+ aud: request.audience,
283
+ sub: binding.bindingId,
284
+ iat: Math.floor(Date.now() / 1e3),
285
+ claims: request.requestedClaims
286
+ };
287
+ return encoder3.encode(JSON.stringify(payload)).buffer;
288
+ }
289
+ function needsRenewal(binding) {
290
+ const sevenDays = 7 * 24 * 60 * 60 * 1e3;
291
+ return binding.certExpiry.getTime() - Date.now() < sevenDays;
292
+ }
293
+ async function getExpiringSoon() {
294
+ const { listBindings: listBindings2 } = await Promise.resolve().then(() => _interopRequireWildcard(require("../storage-4CB2JTLW.cjs")));
295
+ const all = await listBindings2();
296
+ return all.filter((b) => needsRenewal(b));
297
+ }
298
+
299
+ // src/browser-vp/index.ts
300
+ async function detectStrategy() {
301
+ if (typeof window === "undefined") return null;
302
+ if (!_chunkC3Q766CYcjs.isIndexedDbAvailable.call(void 0, )) return null;
303
+ if (!_optionalChain([window, 'access', _10 => _10.crypto, 'optionalAccess', _11 => _11.subtle])) return null;
304
+ if (await hasPrfSupport()) {
305
+ return "prf";
306
+ }
307
+ return "device-key";
308
+ }
309
+ async function initBrowserVp(profileId) {
310
+ const strategy = await detectStrategy();
311
+ if (!strategy) return null;
312
+ const { loadProfileState: loadProfileState2 } = await Promise.resolve().then(() => _interopRequireWildcard(require("../storage-4CB2JTLW.cjs")));
313
+ const state = await loadProfileState2(profileId);
314
+ return {
315
+ strategy,
316
+ profileId,
317
+ ready: state != null && state.strategy === strategy
318
+ };
319
+ }
320
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+
330
+
331
+
332
+
333
+
334
+
335
+
336
+
337
+
338
+
339
+
340
+
341
+
342
+
343
+
344
+
345
+
346
+
347
+
348
+
349
+ exports.clearAll = _chunkC3Q766CYcjs.clearAll; exports.createPasskeyWithPrf = createPasskeyWithPrf; exports.createVp = createVp; exports.decrypt = decrypt; exports.deleteBinding = _chunkC3Q766CYcjs.deleteBinding; exports.deleteProfileState = _chunkC3Q766CYcjs.deleteProfileState; exports.deriveKeysFromPrf = deriveKeysFromPrf; exports.deriveWrappingKey = deriveWrappingKey; exports.detectStrategy = detectStrategy; exports.encrypt = encrypt; exports.evaluatePrf = evaluatePrf; exports.exportPublicKey = exportPublicKey; exports.generateDataKey = generateDataKey; exports.generateDeviceKey = generateDeviceKey; exports.getExpiringSoon = getExpiringSoon; exports.hasPrfSupport = hasPrfSupport; exports.initBrowserVp = initBrowserVp; exports.isIndexedDbAvailable = _chunkC3Q766CYcjs.isIndexedDbAvailable; exports.listBindings = _chunkC3Q766CYcjs.listBindings; exports.loadBinding = _chunkC3Q766CYcjs.loadBinding; exports.loadProfileState = _chunkC3Q766CYcjs.loadProfileState; exports.needsRenewal = needsRenewal; exports.saveBinding = _chunkC3Q766CYcjs.saveBinding; exports.saveProfileState = _chunkC3Q766CYcjs.saveProfileState; exports.sha256 = sha256; exports.sign = sign; exports.unwrapSigningKey = unwrapSigningKey; exports.wrapKey = wrapKey;
@@ -0,0 +1,287 @@
1
+ /**
2
+ * Browser VP types.
3
+ *
4
+ * See arch/auth/browser-vp.md for full specification.
5
+ */
6
+ /** VP strategy — automatically selected based on browser capabilities. */
7
+ type VpStrategy = "prf" | "device-key";
8
+ /** Profile state stored in IndexedDB. */
9
+ interface ProfileState {
10
+ /** Which strategy this profile uses. */
11
+ strategy: VpStrategy;
12
+ /** Encrypted profile blob (< 1MB typical). */
13
+ encryptedBlob: ArrayBuffer;
14
+ /** Optimistic sync version with SID server. */
15
+ blobVersion: number;
16
+ /** PRF salt identifier (for versioning). */
17
+ prfSalt?: string;
18
+ /** SHA-256 of master public key (authenticator change detection). */
19
+ masterPublicKeyHash?: string;
20
+ /** Non-extractable CryptoKey reference (browser-managed). */
21
+ cryptoKeyRef?: CryptoKey;
22
+ /** Last sync timestamp with SID server. */
23
+ lastSyncedAt: Date;
24
+ /** SID device ID for this browser instance. */
25
+ deviceId: string;
26
+ }
27
+ /** Per-service binding stored in IndexedDB. */
28
+ interface BindingEntry {
29
+ /** Opaque BindingId (pairwise per-service). */
30
+ bindingId: string;
31
+ /** HD derivation index for this binding. */
32
+ bindingIndex: number;
33
+ /** Per-binding certificate chain (DER-encoded). */
34
+ certChain: ArrayBuffer;
35
+ /** Certificate expiry date. */
36
+ certExpiry: Date;
37
+ /** Last time a VP was created for this binding. */
38
+ lastUsedAt: Date;
39
+ }
40
+ /** Key material derived from PRF or generated via WebCrypto. */
41
+ interface DerivedKeys {
42
+ /** ECDSA P-256 signing key (non-extractable). */
43
+ signingKey: CryptoKeyPair;
44
+ /** AES-256-GCM data encryption key. */
45
+ dataKey: CryptoKey;
46
+ }
47
+ /** Verifiable Presentation payload. */
48
+ interface VerifiablePresentation {
49
+ /** The service sector this VP is bound to. */
50
+ serviceSector: string;
51
+ /** Binding ID (pairwise). */
52
+ bindingId: string;
53
+ /** Signed VP payload (CBOR or JSON). */
54
+ payload: ArrayBuffer;
55
+ /** Signature over payload. */
56
+ signature: ArrayBuffer;
57
+ /** Certificate chain for verification. */
58
+ certChain: ArrayBuffer;
59
+ /** Nonce from relying party (replay protection). */
60
+ nonce: string;
61
+ /** Audience (relying party origin). */
62
+ audience: string;
63
+ }
64
+ /** Options for VP creation request from a relying party. */
65
+ interface VpRequest {
66
+ /** Nonce for replay protection. */
67
+ nonce: string;
68
+ /** Requested claims (e.g., ["email", "name"]). */
69
+ requestedClaims: string[];
70
+ /** Audience (relying party origin). */
71
+ audience: string;
72
+ /** Service sector identifier. */
73
+ serviceSector: string;
74
+ }
75
+ /** Result of browser VP initialization. */
76
+ interface BrowserVpContext {
77
+ /** Active strategy. */
78
+ strategy: VpStrategy;
79
+ /** Profile ID. */
80
+ profileId: string;
81
+ /** Whether VP creation is available (keys + blob + cert ready). */
82
+ ready: boolean;
83
+ }
84
+
85
+ /**
86
+ * WebAuthn PRF extension support (Strategy A).
87
+ *
88
+ * PRF (Pseudo-Random Function) extension allows deriving
89
+ * deterministic secrets from a passkey. Same passkey on any
90
+ * synced device produces the same PRF output → same keys.
91
+ *
92
+ * See arch/auth/browser-vp.md § Strategy A: Passkey-Derived PPK.
93
+ */
94
+ /**
95
+ * Detect whether the browser supports WebAuthn PRF extension.
96
+ *
97
+ * Attempts a PRF evaluation on an existing passkey. Returns false
98
+ * if PRF is unsupported — no error thrown.
99
+ *
100
+ * Browser support (2026): Chrome 116+, Edge 116+, Safari 17.4+,
101
+ * Chrome Android, Safari iOS 17.4+. ~90% coverage.
102
+ */
103
+ declare function hasPrfSupport(): Promise<boolean>;
104
+ /**
105
+ * Evaluate PRF extension during passkey authentication.
106
+ *
107
+ * Returns the raw PRF output (32 bytes) which serves as the
108
+ * entropy source for key derivation.
109
+ *
110
+ * @param credentialId - Optional: specific credential to use.
111
+ * @returns PRF output bytes, or null if PRF evaluation failed.
112
+ */
113
+ declare function evaluatePrf(credentialId?: ArrayBuffer): Promise<ArrayBuffer | null>;
114
+ /**
115
+ * Create a passkey with PRF extension (first-time setup).
116
+ *
117
+ * Returns both the credential and the initial PRF output.
118
+ */
119
+ declare function createPasskeyWithPrf(userId: ArrayBuffer, userName: string, rpName: string): Promise<{
120
+ credential: PublicKeyCredential;
121
+ prfOutput: ArrayBuffer;
122
+ } | null>;
123
+
124
+ /**
125
+ * Cryptographic primitives for Browser VP.
126
+ *
127
+ * All operations use Web Crypto API. Keys are non-extractable.
128
+ * See arch/auth/browser-vp.md § Key derivation.
129
+ */
130
+
131
+ /**
132
+ * Derive signing key (ECDSA P-256) and data key (AES-256-GCM)
133
+ * from a PRF output using HKDF-SHA256.
134
+ *
135
+ * Both keys are deterministic from the same PRF seed.
136
+ * Same passkey on any device → same keys.
137
+ */
138
+ declare function deriveKeysFromPrf(prfOutput: ArrayBuffer): Promise<DerivedKeys>;
139
+ /**
140
+ * Generate a random ECDSA P-256 key pair (Strategy B).
141
+ * The private key is non-extractable.
142
+ */
143
+ declare function generateDeviceKey(): Promise<CryptoKeyPair>;
144
+ /**
145
+ * Generate a random AES-256-GCM data key (Strategy B).
146
+ */
147
+ declare function generateDataKey(): Promise<CryptoKey>;
148
+ /**
149
+ * Wrap a CryptoKey with AES-KW for upload to SID (Strategy B).
150
+ * Returns the wrapped key bytes.
151
+ */
152
+ declare function wrapKey(keyToWrap: CryptoKey, wrappingKey: CryptoKey): Promise<ArrayBuffer>;
153
+ /**
154
+ * Unwrap a CryptoKey from SID (Strategy B, new browser).
155
+ * Returns non-extractable ECDSA P-256 key pair.
156
+ */
157
+ declare function unwrapSigningKey(wrappedKey: ArrayBuffer, wrappingKey: CryptoKey): Promise<CryptoKey>;
158
+ /**
159
+ * Derive AES-256-KW wrapping key from OPAQUE session export key (Strategy B).
160
+ */
161
+ declare function deriveWrappingKey(sessionExportKey: ArrayBuffer): Promise<CryptoKey>;
162
+ /**
163
+ * Encrypt data with AES-256-GCM.
164
+ * Returns IV (12 bytes) prepended to ciphertext.
165
+ */
166
+ declare function encrypt(data: ArrayBuffer, key: CryptoKey): Promise<ArrayBuffer>;
167
+ /**
168
+ * Decrypt data encrypted with `encrypt()`.
169
+ * Expects IV (12 bytes) prepended to ciphertext.
170
+ */
171
+ declare function decrypt(data: ArrayBuffer, key: CryptoKey): Promise<ArrayBuffer>;
172
+ /**
173
+ * Sign data with ECDSA P-256 (SHA-256).
174
+ */
175
+ declare function sign(data: ArrayBuffer, privateKey: CryptoKey): Promise<ArrayBuffer>;
176
+ /**
177
+ * Export public key as raw bytes (for CSR / certificate request).
178
+ */
179
+ declare function exportPublicKey(keyPair: CryptoKeyPair): Promise<ArrayBuffer>;
180
+ /**
181
+ * Compute SHA-256 hash (for authenticator change detection).
182
+ */
183
+ declare function sha256(data: ArrayBuffer): Promise<string>;
184
+
185
+ /**
186
+ * IndexedDB storage for Browser VP.
187
+ *
188
+ * Database: sid-browser-vp
189
+ * ├── bindings — per-service binding entries (key: service_sector)
190
+ * └── profile-state — profile state + encrypted blob (key: profile_id)
191
+ *
192
+ * See arch/auth/browser-vp.md § IndexedDB Storage Schema.
193
+ */
194
+
195
+ /** Save or update profile state. */
196
+ declare function saveProfileState(profileId: string, state: ProfileState): Promise<void>;
197
+ /** Load profile state by ID. Returns null if not found. */
198
+ declare function loadProfileState(profileId: string): Promise<ProfileState | null>;
199
+ /** Delete profile state. */
200
+ declare function deleteProfileState(profileId: string): Promise<void>;
201
+ /** Save or update a binding entry. */
202
+ declare function saveBinding(serviceSector: string, entry: BindingEntry): Promise<void>;
203
+ /** Load a binding by service sector. */
204
+ declare function loadBinding(serviceSector: string): Promise<BindingEntry | null>;
205
+ /** List all stored bindings. */
206
+ declare function listBindings(): Promise<Array<{
207
+ serviceSector: string;
208
+ } & BindingEntry>>;
209
+ /** Delete a binding. */
210
+ declare function deleteBinding(serviceSector: string): Promise<void>;
211
+ /** Clear all data (profile state + bindings). */
212
+ declare function clearAll(): Promise<void>;
213
+ /** Check if IndexedDB is available in this environment. */
214
+ declare function isIndexedDbAvailable(): boolean;
215
+
216
+ /**
217
+ * Verifiable Presentation creation.
218
+ *
219
+ * Builds and signs VPs using cached keys and profile blob.
220
+ * VP creation is fully offline (~50ms with biometric cache).
221
+ *
222
+ * See arch/auth/browser-vp.md § VP Creation.
223
+ */
224
+
225
+ /**
226
+ * Create a Verifiable Presentation for a service.
227
+ *
228
+ * @param request - VP request from the relying party.
229
+ * @param signingKey - ECDSA P-256 private key (master or per-binding).
230
+ * @param profileBlob - Decrypted profile blob.
231
+ * @returns Signed VP, or null if binding not found.
232
+ */
233
+ declare function createVp(request: VpRequest, signingKey: CryptoKey, profileBlob: ArrayBuffer): Promise<VerifiablePresentation | null>;
234
+ /**
235
+ * Check if a binding's certificate needs renewal.
236
+ *
237
+ * Renewal is attempted 7 days before expiry (background).
238
+ */
239
+ declare function needsRenewal(binding: BindingEntry): boolean;
240
+ /**
241
+ * List all bindings that need certificate renewal.
242
+ */
243
+ declare function getExpiringSoon(): Promise<Array<{
244
+ serviceSector: string;
245
+ } & BindingEntry>>;
246
+
247
+ /**
248
+ * Browser VP — Zero-Load Auth in Browser.
249
+ *
250
+ * Public API for browser-based Verifiable Presentation creation.
251
+ * Automatically selects the best strategy based on browser capabilities:
252
+ *
253
+ * - **Strategy A (PRF):** Passkey PRF extension → deterministic keys.
254
+ * Cross-device via passkey sync. ~90% browser coverage (2026).
255
+ *
256
+ * - **Strategy B (device-key):** Random WebCrypto key, wrapped for sync.
257
+ * Each browser = separate device. Fallback for non-PRF browsers.
258
+ *
259
+ * See arch/auth/browser-vp.md for full specification.
260
+ *
261
+ * Reference: Prudnikov, D. (2026). "Client-Side Verifiable Presentation System
262
+ * with Passkey-Derived Persistent Keys and Pairwise Service Bindings."
263
+ * doi:10.5281/zenodo.19387768 — https://doi.org/10.5281/zenodo.19387768
264
+ */
265
+
266
+ /**
267
+ * Detect the best VP strategy for this browser.
268
+ *
269
+ * Returns "prf" if WebAuthn PRF extension is supported,
270
+ * "device-key" otherwise. Returns null if Browser VP is
271
+ * not possible (no IndexedDB, no WebCrypto).
272
+ */
273
+ declare function detectStrategy(): Promise<VpStrategy | null>;
274
+ /**
275
+ * Initialize Browser VP for a profile.
276
+ *
277
+ * This is the main entry point. It:
278
+ * 1. Detects the best strategy
279
+ * 2. Checks for existing state in IndexedDB
280
+ * 3. Returns a context object indicating readiness
281
+ *
282
+ * If not ready, the caller should trigger setup flow
283
+ * (passkey creation for PRF, or key generation for device-key).
284
+ */
285
+ declare function initBrowserVp(profileId: string): Promise<BrowserVpContext | null>;
286
+
287
+ export { type BindingEntry, type BrowserVpContext, type DerivedKeys, type ProfileState, type VerifiablePresentation, type VpRequest, type VpStrategy, clearAll, createPasskeyWithPrf, createVp, decrypt, deleteBinding, deleteProfileState, deriveKeysFromPrf, deriveWrappingKey, detectStrategy, encrypt, evaluatePrf, exportPublicKey, generateDataKey, generateDeviceKey, getExpiringSoon, hasPrfSupport, initBrowserVp, isIndexedDbAvailable, listBindings, loadBinding, loadProfileState, needsRenewal, saveBinding, saveProfileState, sha256, sign, unwrapSigningKey, wrapKey };