@auths-dev/sdk 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/Cargo.toml +45 -0
  2. package/README.md +163 -4
  3. package/__test__/client.spec.ts +78 -0
  4. package/__test__/exports.spec.ts +57 -0
  5. package/__test__/integration.spec.ts +407 -0
  6. package/__test__/policy.spec.ts +202 -0
  7. package/__test__/verify.spec.ts +88 -0
  8. package/build.rs +5 -0
  9. package/index.d.ts +259 -0
  10. package/index.js +622 -1
  11. package/lib/artifacts.ts +124 -0
  12. package/lib/attestations.ts +126 -0
  13. package/lib/audit.ts +189 -0
  14. package/lib/client.ts +293 -0
  15. package/lib/commits.ts +70 -0
  16. package/lib/devices.ts +178 -0
  17. package/lib/errors.ts +306 -0
  18. package/lib/identity.ts +280 -0
  19. package/lib/index.ts +125 -0
  20. package/lib/native.ts +255 -0
  21. package/lib/org.ts +235 -0
  22. package/lib/pairing.ts +271 -0
  23. package/lib/policy.ts +669 -0
  24. package/lib/signing.ts +204 -0
  25. package/lib/trust.ts +152 -0
  26. package/lib/types.ts +179 -0
  27. package/lib/verify.ts +241 -0
  28. package/lib/witness.ts +91 -0
  29. package/npm/darwin-arm64/README.md +3 -0
  30. package/npm/darwin-arm64/package.json +23 -0
  31. package/npm/linux-arm64-gnu/README.md +3 -0
  32. package/npm/linux-arm64-gnu/package.json +26 -0
  33. package/npm/linux-x64-gnu/README.md +3 -0
  34. package/npm/linux-x64-gnu/package.json +26 -0
  35. package/npm/win32-arm64-msvc/README.md +3 -0
  36. package/npm/win32-arm64-msvc/package.json +23 -0
  37. package/npm/win32-x64-msvc/README.md +3 -0
  38. package/npm/win32-x64-msvc/package.json +23 -0
  39. package/package.json +51 -16
  40. package/src/artifact.rs +217 -0
  41. package/src/attestation_query.rs +104 -0
  42. package/src/audit.rs +128 -0
  43. package/src/commit_sign.rs +63 -0
  44. package/src/device.rs +212 -0
  45. package/src/diagnostics.rs +106 -0
  46. package/src/error.rs +5 -0
  47. package/src/helpers.rs +60 -0
  48. package/src/identity.rs +467 -0
  49. package/src/lib.rs +26 -0
  50. package/src/org.rs +430 -0
  51. package/src/pairing.rs +454 -0
  52. package/src/policy.rs +147 -0
  53. package/src/sign.rs +215 -0
  54. package/src/trust.rs +189 -0
  55. package/src/types.rs +205 -0
  56. package/src/verify.rs +447 -0
  57. package/src/witness.rs +138 -0
  58. package/tsconfig.json +19 -0
  59. package/typedoc.json +18 -0
  60. package/vitest.config.ts +12 -0
package/src/verify.rs ADDED
@@ -0,0 +1,447 @@
1
+ use auths_verifier::core::{
2
+ Attestation, Capability, MAX_ATTESTATION_JSON_SIZE, MAX_JSON_BATCH_SIZE,
3
+ };
4
+ use auths_verifier::error::AuthsErrorInfo;
5
+ use auths_verifier::types::DeviceDID;
6
+ use auths_verifier::verify::{
7
+ verify_at_time as rust_verify_at_time, verify_chain as rust_verify_chain,
8
+ verify_chain_with_capability as rust_verify_chain_with_capability,
9
+ verify_chain_with_witnesses as rust_verify_chain_with_witnesses,
10
+ verify_device_authorization as rust_verify_device_authorization,
11
+ verify_with_capability as rust_verify_with_capability, verify_with_keys,
12
+ };
13
+ use auths_verifier::witness::{WitnessReceipt, WitnessVerifyConfig};
14
+ use chrono::{DateTime, Utc};
15
+ use napi_derive::napi;
16
+
17
+ use crate::error::format_error;
18
+ use crate::types::{NapiVerificationReport, NapiVerificationResult};
19
+
20
+ fn decode_pk_hex(hex_str: &str, label: &str) -> napi::Result<Vec<u8>> {
21
+ let bytes = hex::decode(hex_str)
22
+ .map_err(|e| format_error("AUTHS_INVALID_INPUT", format!("Invalid {label} hex: {e}")))?;
23
+ if bytes.len() != 32 {
24
+ return Err(format_error(
25
+ "AUTHS_INVALID_INPUT",
26
+ format!(
27
+ "Invalid {label} length: expected 32 bytes (64 hex chars), got {}",
28
+ bytes.len()
29
+ ),
30
+ ));
31
+ }
32
+ Ok(bytes)
33
+ }
34
+
35
+ fn parse_attestations(jsons: &[String]) -> napi::Result<Vec<Attestation>> {
36
+ jsons
37
+ .iter()
38
+ .enumerate()
39
+ .map(|(i, json)| {
40
+ serde_json::from_str(json).map_err(|e| {
41
+ format_error(
42
+ "AUTHS_SERIALIZATION_ERROR",
43
+ format!("Failed to parse attestation {i}: {e}"),
44
+ )
45
+ })
46
+ })
47
+ .collect()
48
+ }
49
+
50
+ fn check_batch_size(jsons: &[String]) -> napi::Result<()> {
51
+ let total: usize = jsons.iter().map(|s| s.len()).sum();
52
+ if total > MAX_JSON_BATCH_SIZE {
53
+ return Err(format_error(
54
+ "AUTHS_INVALID_INPUT",
55
+ format!("Total attestation JSON too large: {total} bytes, max {MAX_JSON_BATCH_SIZE}"),
56
+ ));
57
+ }
58
+ Ok(())
59
+ }
60
+
61
+ #[napi]
62
+ pub async fn verify_attestation(
63
+ attestation_json: String,
64
+ issuer_pk_hex: String,
65
+ ) -> napi::Result<NapiVerificationResult> {
66
+ if attestation_json.len() > MAX_ATTESTATION_JSON_SIZE {
67
+ return Err(format_error(
68
+ "AUTHS_INVALID_INPUT",
69
+ format!(
70
+ "Attestation JSON too large: {} bytes, max {}",
71
+ attestation_json.len(),
72
+ MAX_ATTESTATION_JSON_SIZE
73
+ ),
74
+ ));
75
+ }
76
+
77
+ let issuer_pk_bytes = decode_pk_hex(&issuer_pk_hex, "issuer public key")?;
78
+
79
+ let att: Attestation = match serde_json::from_str(&attestation_json) {
80
+ Ok(att) => att,
81
+ Err(e) => {
82
+ return Ok(NapiVerificationResult {
83
+ valid: false,
84
+ error: Some(format!("Failed to parse attestation JSON: {e}")),
85
+ error_code: Some("AUTHS_SERIALIZATION_ERROR".to_string()),
86
+ });
87
+ }
88
+ };
89
+
90
+ match verify_with_keys(&att, &issuer_pk_bytes).await {
91
+ Ok(_) => Ok(NapiVerificationResult {
92
+ valid: true,
93
+ error: None,
94
+ error_code: None,
95
+ }),
96
+ Err(e) => Ok(NapiVerificationResult {
97
+ valid: false,
98
+ error_code: Some(e.error_code().to_string()),
99
+ error: Some(e.to_string()),
100
+ }),
101
+ }
102
+ }
103
+
104
+ #[napi]
105
+ pub async fn verify_chain(
106
+ attestations_json: Vec<String>,
107
+ root_pk_hex: String,
108
+ ) -> napi::Result<NapiVerificationReport> {
109
+ check_batch_size(&attestations_json)?;
110
+ let root_pk_bytes = decode_pk_hex(&root_pk_hex, "root public key")?;
111
+ let attestations = parse_attestations(&attestations_json)?;
112
+
113
+ match rust_verify_chain(&attestations, &root_pk_bytes).await {
114
+ Ok(report) => Ok(report.into()),
115
+ Err(e) => Err(format_error(
116
+ e.error_code(),
117
+ format!("Chain verification failed: {e}"),
118
+ )),
119
+ }
120
+ }
121
+
122
+ #[napi]
123
+ pub async fn verify_device_authorization(
124
+ identity_did: String,
125
+ device_did: String,
126
+ attestations_json: Vec<String>,
127
+ identity_pk_hex: String,
128
+ ) -> napi::Result<NapiVerificationReport> {
129
+ check_batch_size(&attestations_json)?;
130
+ let identity_pk_bytes = decode_pk_hex(&identity_pk_hex, "identity public key")?;
131
+ let attestations = parse_attestations(&attestations_json)?;
132
+ let device =
133
+ DeviceDID::parse(&device_did).map_err(|e| format_error("AUTHS_INVALID_INPUT", e))?;
134
+
135
+ match rust_verify_device_authorization(
136
+ &identity_did,
137
+ &device,
138
+ &attestations,
139
+ &identity_pk_bytes,
140
+ )
141
+ .await
142
+ {
143
+ Ok(report) => Ok(report.into()),
144
+ Err(e) => Err(format_error(
145
+ e.error_code(),
146
+ format!("Device authorization verification failed: {e}"),
147
+ )),
148
+ }
149
+ }
150
+
151
+ #[napi]
152
+ pub async fn verify_attestation_with_capability(
153
+ attestation_json: String,
154
+ issuer_pk_hex: String,
155
+ required_capability: String,
156
+ ) -> napi::Result<NapiVerificationResult> {
157
+ if attestation_json.len() > MAX_ATTESTATION_JSON_SIZE {
158
+ return Err(format_error(
159
+ "AUTHS_INVALID_INPUT",
160
+ format!(
161
+ "Attestation JSON too large: {} bytes, max {}",
162
+ attestation_json.len(),
163
+ MAX_ATTESTATION_JSON_SIZE
164
+ ),
165
+ ));
166
+ }
167
+
168
+ let issuer_pk_bytes = decode_pk_hex(&issuer_pk_hex, "issuer public key")?;
169
+
170
+ let att: Attestation = match serde_json::from_str(&attestation_json) {
171
+ Ok(att) => att,
172
+ Err(e) => {
173
+ return Ok(NapiVerificationResult {
174
+ valid: false,
175
+ error: Some(format!("Failed to parse attestation JSON: {e}")),
176
+ error_code: Some("AUTHS_SERIALIZATION_ERROR".to_string()),
177
+ });
178
+ }
179
+ };
180
+
181
+ let cap = Capability::parse(&required_capability).map_err(|e| {
182
+ format_error(
183
+ "AUTHS_INVALID_INPUT",
184
+ format!("Invalid capability '{required_capability}': {e}"),
185
+ )
186
+ })?;
187
+
188
+ match rust_verify_with_capability(&att, &cap, &issuer_pk_bytes).await {
189
+ Ok(_) => Ok(NapiVerificationResult {
190
+ valid: true,
191
+ error: None,
192
+ error_code: None,
193
+ }),
194
+ Err(e) => Ok(NapiVerificationResult {
195
+ valid: false,
196
+ error_code: Some(e.error_code().to_string()),
197
+ error: Some(e.to_string()),
198
+ }),
199
+ }
200
+ }
201
+
202
+ #[napi]
203
+ pub async fn verify_chain_with_capability(
204
+ attestations_json: Vec<String>,
205
+ root_pk_hex: String,
206
+ required_capability: String,
207
+ ) -> napi::Result<NapiVerificationReport> {
208
+ check_batch_size(&attestations_json)?;
209
+ let root_pk_bytes = decode_pk_hex(&root_pk_hex, "root public key")?;
210
+ let attestations = parse_attestations(&attestations_json)?;
211
+
212
+ let cap = Capability::parse(&required_capability).map_err(|e| {
213
+ format_error(
214
+ "AUTHS_INVALID_INPUT",
215
+ format!("Invalid capability '{required_capability}': {e}"),
216
+ )
217
+ })?;
218
+
219
+ match rust_verify_chain_with_capability(&attestations, &cap, &root_pk_bytes).await {
220
+ Ok(report) => Ok(report.into()),
221
+ Err(e) => Err(format_error(
222
+ e.error_code(),
223
+ format!("Chain verification with capability failed: {e}"),
224
+ )),
225
+ }
226
+ }
227
+
228
+ fn parse_rfc3339_timestamp(at_rfc3339: &str) -> napi::Result<DateTime<Utc>> {
229
+ let at: DateTime<Utc> = at_rfc3339.parse::<DateTime<Utc>>().map_err(|_| {
230
+ if at_rfc3339.contains(' ') && !at_rfc3339.contains('T') {
231
+ format_error(
232
+ "AUTHS_INVALID_INPUT",
233
+ format!(
234
+ "Expected RFC 3339 format like '2024-06-15T00:00:00Z', got '{at_rfc3339}'. \
235
+ Hint: use 'T' between date and time, and append 'Z' or a UTC offset."
236
+ ),
237
+ )
238
+ } else {
239
+ format_error(
240
+ "AUTHS_INVALID_INPUT",
241
+ format!(
242
+ "Expected RFC 3339 format like '2024-06-15T00:00:00Z', got '{at_rfc3339}'."
243
+ ),
244
+ )
245
+ }
246
+ })?;
247
+
248
+ #[allow(clippy::disallowed_methods)] // Presentation boundary
249
+ let now = Utc::now();
250
+ let skew_tolerance = chrono::Duration::seconds(60);
251
+ if at > now + skew_tolerance {
252
+ return Err(format_error(
253
+ "AUTHS_INVALID_INPUT",
254
+ format!(
255
+ "Timestamp {at_rfc3339} is in the future. \
256
+ Time-pinned verification requires a past or present timestamp."
257
+ ),
258
+ ));
259
+ }
260
+
261
+ Ok(at)
262
+ }
263
+
264
+ #[napi]
265
+ pub async fn verify_at_time(
266
+ attestation_json: String,
267
+ issuer_pk_hex: String,
268
+ at_rfc3339: String,
269
+ ) -> napi::Result<NapiVerificationResult> {
270
+ if attestation_json.len() > MAX_ATTESTATION_JSON_SIZE {
271
+ return Err(format_error(
272
+ "AUTHS_INVALID_INPUT",
273
+ format!(
274
+ "Attestation JSON too large: {} bytes, max {}",
275
+ attestation_json.len(),
276
+ MAX_ATTESTATION_JSON_SIZE
277
+ ),
278
+ ));
279
+ }
280
+
281
+ let at = parse_rfc3339_timestamp(&at_rfc3339)?;
282
+ let issuer_pk_bytes = decode_pk_hex(&issuer_pk_hex, "issuer public key")?;
283
+
284
+ let att: Attestation = match serde_json::from_str(&attestation_json) {
285
+ Ok(att) => att,
286
+ Err(e) => {
287
+ return Ok(NapiVerificationResult {
288
+ valid: false,
289
+ error: Some(format!("Failed to parse attestation JSON: {e}")),
290
+ error_code: Some("AUTHS_SERIALIZATION_ERROR".to_string()),
291
+ });
292
+ }
293
+ };
294
+
295
+ match rust_verify_at_time(&att, &issuer_pk_bytes, at).await {
296
+ Ok(_) => Ok(NapiVerificationResult {
297
+ valid: true,
298
+ error: None,
299
+ error_code: None,
300
+ }),
301
+ Err(e) => Ok(NapiVerificationResult {
302
+ valid: false,
303
+ error_code: Some(e.error_code().to_string()),
304
+ error: Some(e.to_string()),
305
+ }),
306
+ }
307
+ }
308
+
309
+ #[napi]
310
+ pub async fn verify_at_time_with_capability(
311
+ attestation_json: String,
312
+ issuer_pk_hex: String,
313
+ at_rfc3339: String,
314
+ required_capability: String,
315
+ ) -> napi::Result<NapiVerificationResult> {
316
+ if attestation_json.len() > MAX_ATTESTATION_JSON_SIZE {
317
+ return Err(format_error(
318
+ "AUTHS_INVALID_INPUT",
319
+ format!(
320
+ "Attestation JSON too large: {} bytes, max {}",
321
+ attestation_json.len(),
322
+ MAX_ATTESTATION_JSON_SIZE
323
+ ),
324
+ ));
325
+ }
326
+
327
+ let at = parse_rfc3339_timestamp(&at_rfc3339)?;
328
+ let issuer_pk_bytes = decode_pk_hex(&issuer_pk_hex, "issuer public key")?;
329
+
330
+ let att: Attestation = match serde_json::from_str(&attestation_json) {
331
+ Ok(att) => att,
332
+ Err(e) => {
333
+ return Ok(NapiVerificationResult {
334
+ valid: false,
335
+ error: Some(format!("Failed to parse attestation JSON: {e}")),
336
+ error_code: Some("AUTHS_SERIALIZATION_ERROR".to_string()),
337
+ });
338
+ }
339
+ };
340
+
341
+ let cap = Capability::parse(&required_capability).map_err(|e| {
342
+ format_error(
343
+ "AUTHS_INVALID_INPUT",
344
+ format!("Invalid capability '{required_capability}': {e}"),
345
+ )
346
+ })?;
347
+
348
+ match rust_verify_at_time(&att, &issuer_pk_bytes, at).await {
349
+ Ok(_) => {
350
+ if att.capabilities.contains(&cap) {
351
+ Ok(NapiVerificationResult {
352
+ valid: true,
353
+ error: None,
354
+ error_code: None,
355
+ })
356
+ } else {
357
+ Ok(NapiVerificationResult {
358
+ valid: false,
359
+ error: Some(format!(
360
+ "Attestation does not grant required capability '{required_capability}'"
361
+ )),
362
+ error_code: Some("AUTHS_MISSING_CAPABILITY".to_string()),
363
+ })
364
+ }
365
+ }
366
+ Err(e) => Ok(NapiVerificationResult {
367
+ valid: false,
368
+ error_code: Some(e.error_code().to_string()),
369
+ error: Some(e.to_string()),
370
+ }),
371
+ }
372
+ }
373
+
374
+ #[napi]
375
+ pub async fn verify_chain_with_witnesses(
376
+ attestations_json: Vec<String>,
377
+ root_pk_hex: String,
378
+ receipts_json: Vec<String>,
379
+ witness_keys_json: Vec<String>,
380
+ threshold: u32,
381
+ ) -> napi::Result<NapiVerificationReport> {
382
+ check_batch_size(&attestations_json)?;
383
+ let root_pk_bytes = decode_pk_hex(&root_pk_hex, "root public key")?;
384
+ let attestations = parse_attestations(&attestations_json)?;
385
+
386
+ let receipts: Vec<WitnessReceipt> = receipts_json
387
+ .iter()
388
+ .enumerate()
389
+ .map(|(i, json)| {
390
+ serde_json::from_str(json).map_err(|e| {
391
+ format_error(
392
+ "AUTHS_SERIALIZATION_ERROR",
393
+ format!("Failed to parse witness receipt {i}: {e}"),
394
+ )
395
+ })
396
+ })
397
+ .collect::<napi::Result<Vec<_>>>()?;
398
+
399
+ #[derive(serde::Deserialize)]
400
+ struct WitnessKeyInput {
401
+ did: String,
402
+ public_key_hex: String,
403
+ }
404
+
405
+ let witness_keys: Vec<(String, Vec<u8>)> = witness_keys_json
406
+ .iter()
407
+ .enumerate()
408
+ .map(|(i, json)| {
409
+ let input: WitnessKeyInput = serde_json::from_str(json).map_err(|e| {
410
+ format_error(
411
+ "AUTHS_SERIALIZATION_ERROR",
412
+ format!("Failed to parse witness key {i}: {e}"),
413
+ )
414
+ })?;
415
+ let pk_bytes = hex::decode(&input.public_key_hex).map_err(|e| {
416
+ format_error(
417
+ "AUTHS_INVALID_INPUT",
418
+ format!("Invalid witness key {i} hex: {e}"),
419
+ )
420
+ })?;
421
+ if pk_bytes.len() != 32 {
422
+ return Err(format_error(
423
+ "AUTHS_INVALID_INPUT",
424
+ format!(
425
+ "Invalid witness key {i} length: expected 32 bytes, got {}",
426
+ pk_bytes.len()
427
+ ),
428
+ ));
429
+ }
430
+ Ok((input.did, pk_bytes))
431
+ })
432
+ .collect::<napi::Result<Vec<_>>>()?;
433
+
434
+ let config = WitnessVerifyConfig {
435
+ receipts: &receipts,
436
+ witness_keys: &witness_keys,
437
+ threshold: threshold as usize,
438
+ };
439
+
440
+ match rust_verify_chain_with_witnesses(&attestations, &root_pk_bytes, &config).await {
441
+ Ok(report) => Ok(report.into()),
442
+ Err(e) => Err(format_error(
443
+ e.error_code(),
444
+ format!("Chain verification with witnesses failed: {e}"),
445
+ )),
446
+ }
447
+ }
package/src/witness.rs ADDED
@@ -0,0 +1,138 @@
1
+ use std::path::PathBuf;
2
+
3
+ use auths_id::storage::identity::IdentityStorage;
4
+ use auths_id::witness_config::WitnessConfig;
5
+ use auths_storage::git::RegistryIdentityStorage;
6
+ use napi_derive::napi;
7
+
8
+ use crate::error::format_error;
9
+
10
+ fn resolve_repo(repo_path: &str) -> PathBuf {
11
+ PathBuf::from(shellexpand::tilde(repo_path).as_ref())
12
+ }
13
+
14
+ fn load_witness_config(repo_path: &PathBuf) -> napi::Result<WitnessConfig> {
15
+ let storage = RegistryIdentityStorage::new(repo_path);
16
+ let identity = storage
17
+ .load_identity()
18
+ .map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))?;
19
+
20
+ if let Some(wc) = identity
21
+ .metadata
22
+ .as_ref()
23
+ .and_then(|m| m.get("witness_config"))
24
+ {
25
+ let config: WitnessConfig = serde_json::from_value(wc.clone())
26
+ .map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))?;
27
+ return Ok(config);
28
+ }
29
+ Ok(WitnessConfig::default())
30
+ }
31
+
32
+ fn save_witness_config(repo_path: &PathBuf, config: &WitnessConfig) -> napi::Result<()> {
33
+ let storage = RegistryIdentityStorage::new(repo_path);
34
+ let mut identity = storage
35
+ .load_identity()
36
+ .map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))?;
37
+
38
+ let metadata = identity
39
+ .metadata
40
+ .get_or_insert_with(|| serde_json::json!({}));
41
+ if let Some(obj) = metadata.as_object_mut() {
42
+ obj.insert(
43
+ "witness_config".to_string(),
44
+ serde_json::to_value(config).map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))?,
45
+ );
46
+ }
47
+
48
+ storage
49
+ .create_identity(identity.controller_did.as_str(), identity.metadata)
50
+ .map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))?;
51
+ Ok(())
52
+ }
53
+
54
+ #[napi(object)]
55
+ #[derive(Clone)]
56
+ pub struct NapiWitnessResult {
57
+ pub url: String,
58
+ pub did: Option<String>,
59
+ pub label: Option<String>,
60
+ }
61
+
62
+ #[napi]
63
+ pub fn add_witness(
64
+ url_str: String,
65
+ repo_path: String,
66
+ label: Option<String>,
67
+ ) -> napi::Result<NapiWitnessResult> {
68
+ let repo = resolve_repo(&repo_path);
69
+ let parsed_url: url::Url = url_str.parse().map_err(|e| {
70
+ format_error(
71
+ "AUTHS_WITNESS_ERROR",
72
+ format!("Invalid URL '{}': {}", url_str, e),
73
+ )
74
+ })?;
75
+
76
+ let mut config = load_witness_config(&repo)?;
77
+
78
+ if config.witness_urls.contains(&parsed_url) {
79
+ return Ok(NapiWitnessResult {
80
+ url: url_str,
81
+ did: None,
82
+ label,
83
+ });
84
+ }
85
+
86
+ config.witness_urls.push(parsed_url);
87
+ if config.threshold == 0 {
88
+ config.threshold = 1;
89
+ }
90
+
91
+ save_witness_config(&repo, &config)?;
92
+ Ok(NapiWitnessResult {
93
+ url: url_str,
94
+ did: None,
95
+ label,
96
+ })
97
+ }
98
+
99
+ #[napi]
100
+ pub fn remove_witness(url_str: String, repo_path: String) -> napi::Result<()> {
101
+ let repo = resolve_repo(&repo_path);
102
+ let parsed_url: url::Url = url_str.parse().map_err(|e| {
103
+ format_error(
104
+ "AUTHS_WITNESS_ERROR",
105
+ format!("Invalid URL '{}': {}", url_str, e),
106
+ )
107
+ })?;
108
+
109
+ let mut config = load_witness_config(&repo)?;
110
+ config.witness_urls.retain(|u| u != &parsed_url);
111
+
112
+ if config.threshold > config.witness_urls.len() {
113
+ config.threshold = config.witness_urls.len();
114
+ }
115
+
116
+ save_witness_config(&repo, &config)?;
117
+ Ok(())
118
+ }
119
+
120
+ #[napi]
121
+ pub fn list_witnesses(repo_path: String) -> napi::Result<String> {
122
+ let repo = resolve_repo(&repo_path);
123
+ let config = load_witness_config(&repo)?;
124
+
125
+ let entries: Vec<serde_json::Value> = config
126
+ .witness_urls
127
+ .iter()
128
+ .map(|u| {
129
+ serde_json::json!({
130
+ "url": u.to_string(),
131
+ "did": null,
132
+ "label": null,
133
+ })
134
+ })
135
+ .collect();
136
+
137
+ serde_json::to_string(&entries).map_err(|e| format_error("AUTHS_WITNESS_ERROR", e))
138
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "lib": ["ES2022", "ESNext.Disposable"],
5
+ "module": "commonjs",
6
+ "moduleResolution": "node",
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
11
+ "declaration": true,
12
+ "declarationMap": true,
13
+ "sourceMap": true,
14
+ "outDir": "./dist",
15
+ "rootDir": "."
16
+ },
17
+ "include": ["lib/**/*.ts"],
18
+ "exclude": ["node_modules", "__tests__", "dist"]
19
+ }
package/typedoc.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://typedoc.org/schema.json",
3
+ "entryPoints": ["lib/index.ts"],
4
+ "out": "../../docs/sdk/node/api",
5
+ "plugin": ["typedoc-plugin-markdown"],
6
+ "tsconfig": "tsconfig.json",
7
+ "readme": "none",
8
+ "hideGenerator": true,
9
+ "excludePrivate": true,
10
+ "excludeInternal": true,
11
+ "disableSources": true,
12
+ "outputFileStrategy": "modules",
13
+ "hideBreadcrumbs": true,
14
+ "hidePageHeader": true,
15
+ "useCodeBlocks": true,
16
+ "entryFileName": "index",
17
+ "flattenOutputFiles": true
18
+ }
@@ -0,0 +1,12 @@
1
+ import { defineConfig } from 'vitest/config'
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ pool: 'forks',
6
+ poolOptions: {
7
+ forks: {
8
+ singleFork: true,
9
+ },
10
+ },
11
+ },
12
+ })