@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/org.rs ADDED
@@ -0,0 +1,430 @@
1
+ use std::path::PathBuf;
2
+ use std::sync::Arc;
3
+
4
+ use auths_core::ports::clock::SystemClock;
5
+ use auths_core::ports::id::SystemUuidProvider;
6
+ use auths_core::signing::{DidResolver, PrefilledPassphraseProvider, StorageSigner};
7
+ use auths_core::storage::keychain::{IdentityDID, KeyAlias};
8
+ use auths_id::attestation::create::create_signed_attestation;
9
+ use auths_id::identity::initialize::initialize_registry_identity;
10
+ use auths_id::identity::resolve::RegistryDidResolver;
11
+ use auths_id::storage::git_refs::AttestationMetadata;
12
+ use auths_id::storage::registry::{MemberFilter, RegistryBackend};
13
+ use auths_sdk::workflows::org::{
14
+ AddMemberCommand, OrgContext, RevokeMemberCommand, add_organization_member,
15
+ revoke_organization_member,
16
+ };
17
+ use auths_storage::git::{GitRegistryBackend, RegistryConfig};
18
+ use auths_verifier::Capability;
19
+ use auths_verifier::PublicKeyHex;
20
+ use auths_verifier::core::{Ed25519PublicKey, Role};
21
+ use auths_verifier::types::DeviceDID;
22
+ use napi_derive::napi;
23
+
24
+ use crate::error::format_error;
25
+ use crate::helpers::{make_env_config, resolve_passphrase};
26
+
27
+ fn get_keychain(
28
+ passphrase: &str,
29
+ repo_path: &str,
30
+ ) -> napi::Result<Box<dyn auths_core::storage::keychain::KeyStorage + Send + Sync>> {
31
+ let env_config = make_env_config(passphrase, repo_path);
32
+ auths_core::storage::keychain::get_platform_keychain_with_config(&env_config)
33
+ .map_err(|e| format_error("AUTHS_KEYCHAIN_ERROR", e))
34
+ }
35
+
36
+ fn resolve_repo(repo_path: &str) -> PathBuf {
37
+ PathBuf::from(shellexpand::tilde(repo_path).as_ref())
38
+ }
39
+
40
+ fn find_signer_alias(
41
+ org_did: &str,
42
+ keychain: &(dyn auths_core::storage::keychain::KeyStorage + Send + Sync),
43
+ ) -> napi::Result<KeyAlias> {
44
+ let identity_did =
45
+ IdentityDID::parse(org_did).map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
46
+ let aliases = keychain
47
+ .list_aliases_for_identity(&identity_did)
48
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
49
+ aliases
50
+ .into_iter()
51
+ .find(|a| !a.contains("--next-"))
52
+ .ok_or_else(|| {
53
+ format_error(
54
+ "AUTHS_ORG_ERROR",
55
+ format!("No signing key found for org {org_did}"),
56
+ )
57
+ })
58
+ }
59
+
60
+ fn extract_org_prefix(org_did: &str) -> String {
61
+ org_did
62
+ .strip_prefix("did:keri:")
63
+ .unwrap_or(org_did)
64
+ .to_string()
65
+ }
66
+
67
+ #[napi(object)]
68
+ #[derive(Clone)]
69
+ pub struct NapiOrgResult {
70
+ pub org_prefix: String,
71
+ pub org_did: String,
72
+ pub label: String,
73
+ pub repo_path: String,
74
+ }
75
+
76
+ #[napi(object)]
77
+ #[derive(Clone)]
78
+ pub struct NapiOrgMember {
79
+ pub member_did: String,
80
+ pub role: String,
81
+ pub capabilities_json: String,
82
+ pub issuer_did: String,
83
+ pub attestation_rid: String,
84
+ pub revoked: bool,
85
+ pub expires_at: Option<String>,
86
+ }
87
+
88
+ #[napi]
89
+ pub fn create_org(
90
+ label: String,
91
+ repo_path: String,
92
+ passphrase: Option<String>,
93
+ ) -> napi::Result<NapiOrgResult> {
94
+ let passphrase_str = resolve_passphrase(passphrase);
95
+ let repo = resolve_repo(&repo_path);
96
+
97
+ let key_alias_str = format!(
98
+ "org-{}",
99
+ label
100
+ .chars()
101
+ .filter(|c| c.is_alphanumeric())
102
+ .take(20)
103
+ .collect::<String>()
104
+ .to_lowercase()
105
+ );
106
+
107
+ let config = RegistryConfig::single_tenant(&repo);
108
+ let backend = GitRegistryBackend::from_config_unchecked(config);
109
+ backend
110
+ .init_if_needed()
111
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
112
+ let backend = Arc::new(backend);
113
+
114
+ #[allow(clippy::disallowed_methods)] // INVARIANT: key_alias_str from caller input
115
+ let key_alias = KeyAlias::new_unchecked(key_alias_str);
116
+ let keychain = get_keychain(&passphrase_str, &repo_path)?;
117
+ let provider = PrefilledPassphraseProvider::new(&passphrase_str);
118
+
119
+ let (controller_did, alias) =
120
+ initialize_registry_identity(backend.clone(), &key_alias, &provider, &*keychain, None)
121
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
122
+
123
+ let uuid_provider = SystemUuidProvider;
124
+ let rid = auths_core::ports::id::UuidProvider::new_id(&uuid_provider).to_string();
125
+
126
+ let resolver = RegistryDidResolver::new(backend.clone());
127
+ let org_resolved = resolver
128
+ .resolve(controller_did.as_str())
129
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
130
+ let org_pk_bytes = *org_resolved.public_key();
131
+
132
+ #[allow(clippy::disallowed_methods)]
133
+ let now = chrono::Utc::now();
134
+ let admin_capabilities = vec![
135
+ Capability::sign_commit(),
136
+ Capability::sign_release(),
137
+ Capability::manage_members(),
138
+ Capability::rotate_keys(),
139
+ ];
140
+
141
+ let meta = AttestationMetadata {
142
+ note: Some(format!("Organization '{}' root admin", label)),
143
+ timestamp: Some(now),
144
+ expires_at: None,
145
+ };
146
+
147
+ let signer = StorageSigner::new(keychain);
148
+ let org_did_device = DeviceDID::from_ed25519(org_pk_bytes.as_bytes());
149
+
150
+ let attestation = create_signed_attestation(
151
+ now,
152
+ &rid,
153
+ &controller_did,
154
+ &org_did_device,
155
+ org_pk_bytes.as_bytes(),
156
+ Some(serde_json::json!({
157
+ "org_role": "admin",
158
+ "org_name": label
159
+ })),
160
+ &meta,
161
+ &signer,
162
+ &provider,
163
+ Some(&alias),
164
+ None,
165
+ admin_capabilities,
166
+ Some(Role::Admin),
167
+ None,
168
+ )
169
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
170
+
171
+ let org_prefix = extract_org_prefix(controller_did.as_str());
172
+
173
+ backend
174
+ .store_org_member(&org_prefix, &attestation)
175
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
176
+
177
+ Ok(NapiOrgResult {
178
+ org_prefix,
179
+ org_did: controller_did.to_string(),
180
+ label,
181
+ repo_path,
182
+ })
183
+ }
184
+
185
+ #[napi]
186
+ #[allow(clippy::too_many_arguments)]
187
+ pub fn add_org_member(
188
+ org_did: String,
189
+ member_did: String,
190
+ role: String,
191
+ repo_path: String,
192
+ capabilities_json: Option<String>,
193
+ passphrase: Option<String>,
194
+ note: Option<String>,
195
+ member_public_key_hex: Option<String>,
196
+ ) -> napi::Result<NapiOrgMember> {
197
+ let passphrase_str = resolve_passphrase(passphrase);
198
+ let repo = resolve_repo(&repo_path);
199
+
200
+ let role_parsed: Role = role
201
+ .parse()
202
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", format!("Invalid role: {e}")))?;
203
+
204
+ let capabilities: Vec<String> = if let Some(json) = capabilities_json {
205
+ serde_json::from_str(&json).map_err(|e| {
206
+ format_error("AUTHS_ORG_ERROR", format!("Invalid capabilities JSON: {e}"))
207
+ })?
208
+ } else {
209
+ role_parsed
210
+ .default_capabilities()
211
+ .iter()
212
+ .map(|c| c.as_str().to_string())
213
+ .collect()
214
+ };
215
+
216
+ let keychain = get_keychain(&passphrase_str, &repo_path)?;
217
+ let signer_alias = find_signer_alias(&org_did, &*keychain)?;
218
+
219
+ let backend = Arc::new(GitRegistryBackend::from_config_unchecked(
220
+ RegistryConfig::single_tenant(&repo),
221
+ ));
222
+
223
+ let resolver = RegistryDidResolver::new(backend.clone());
224
+ #[allow(clippy::disallowed_methods)] // INVARIANT: hex::encode always produces valid hex
225
+ let admin_pk_hex = PublicKeyHex::new_unchecked(hex::encode(
226
+ resolver
227
+ .resolve(&org_did)
228
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?
229
+ .public_key()
230
+ .as_bytes(),
231
+ ));
232
+
233
+ let member_pk = if let Some(pk_hex) = member_public_key_hex {
234
+ let pk_bytes = hex::decode(&pk_hex).map_err(|e| {
235
+ format_error(
236
+ "AUTHS_ORG_ERROR",
237
+ format!("Invalid member public key hex: {e}"),
238
+ )
239
+ })?;
240
+ Ed25519PublicKey::try_from_slice(&pk_bytes)
241
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?
242
+ } else {
243
+ let member_resolved = resolver
244
+ .resolve(&member_did)
245
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
246
+ *member_resolved.public_key()
247
+ };
248
+
249
+ let org_prefix = extract_org_prefix(&org_did);
250
+
251
+ let signer = StorageSigner::new(keychain);
252
+ let uuid_provider = SystemUuidProvider;
253
+ let provider = PrefilledPassphraseProvider::new(&passphrase_str);
254
+
255
+ let org_ctx = OrgContext {
256
+ registry: &*backend,
257
+ clock: &SystemClock,
258
+ uuid_provider: &uuid_provider,
259
+ signer: &signer,
260
+ passphrase_provider: &provider,
261
+ };
262
+
263
+ let attestation = add_organization_member(
264
+ &org_ctx,
265
+ AddMemberCommand {
266
+ org_prefix,
267
+ member_did: member_did.clone(),
268
+ member_public_key: member_pk,
269
+ role: role_parsed,
270
+ capabilities: capabilities.clone(),
271
+ admin_public_key_hex: admin_pk_hex,
272
+ signer_alias,
273
+ note,
274
+ },
275
+ )
276
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
277
+
278
+ let caps_json = serde_json::to_string(&capabilities).unwrap_or_default();
279
+
280
+ Ok(NapiOrgMember {
281
+ member_did,
282
+ role,
283
+ capabilities_json: caps_json,
284
+ issuer_did: attestation.issuer.to_string(),
285
+ attestation_rid: attestation.rid.to_string(),
286
+ revoked: false,
287
+ expires_at: attestation.expires_at.map(|e| e.to_rfc3339()),
288
+ })
289
+ }
290
+
291
+ #[napi]
292
+ pub fn revoke_org_member(
293
+ org_did: String,
294
+ member_did: String,
295
+ repo_path: String,
296
+ passphrase: Option<String>,
297
+ note: Option<String>,
298
+ member_public_key_hex: Option<String>,
299
+ ) -> napi::Result<NapiOrgMember> {
300
+ let passphrase_str = resolve_passphrase(passphrase);
301
+ let repo = resolve_repo(&repo_path);
302
+
303
+ let keychain = get_keychain(&passphrase_str, &repo_path)?;
304
+ let signer_alias = find_signer_alias(&org_did, &*keychain)?;
305
+
306
+ let backend = Arc::new(GitRegistryBackend::from_config_unchecked(
307
+ RegistryConfig::single_tenant(&repo),
308
+ ));
309
+
310
+ let resolver = RegistryDidResolver::new(backend.clone());
311
+ #[allow(clippy::disallowed_methods)] // INVARIANT: hex::encode always produces valid hex
312
+ let admin_pk_hex = PublicKeyHex::new_unchecked(hex::encode(
313
+ resolver
314
+ .resolve(&org_did)
315
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?
316
+ .public_key()
317
+ .as_bytes(),
318
+ ));
319
+
320
+ let member_pk = if let Some(pk_hex) = member_public_key_hex {
321
+ let pk_bytes = hex::decode(&pk_hex).map_err(|e| {
322
+ format_error(
323
+ "AUTHS_ORG_ERROR",
324
+ format!("Invalid member public key hex: {e}"),
325
+ )
326
+ })?;
327
+ Ed25519PublicKey::try_from_slice(&pk_bytes)
328
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?
329
+ } else {
330
+ let member_resolved = resolver
331
+ .resolve(&member_did)
332
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
333
+ *member_resolved.public_key()
334
+ };
335
+
336
+ let org_prefix = extract_org_prefix(&org_did);
337
+
338
+ let signer = StorageSigner::new(keychain);
339
+ let uuid_provider = SystemUuidProvider;
340
+ let provider = PrefilledPassphraseProvider::new(&passphrase_str);
341
+
342
+ let org_ctx = OrgContext {
343
+ registry: &*backend,
344
+ clock: &SystemClock,
345
+ uuid_provider: &uuid_provider,
346
+ signer: &signer,
347
+ passphrase_provider: &provider,
348
+ };
349
+
350
+ let revocation = revoke_organization_member(
351
+ &org_ctx,
352
+ RevokeMemberCommand {
353
+ org_prefix,
354
+ member_did: member_did.clone(),
355
+ member_public_key: member_pk,
356
+ admin_public_key_hex: admin_pk_hex,
357
+ signer_alias,
358
+ note,
359
+ },
360
+ )
361
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
362
+
363
+ let caps: Vec<String> = revocation
364
+ .capabilities
365
+ .iter()
366
+ .map(|c| c.as_str().to_string())
367
+ .collect();
368
+ let caps_json = serde_json::to_string(&caps).unwrap_or_default();
369
+ let role_str = revocation
370
+ .role
371
+ .map(|r| r.as_str().to_string())
372
+ .unwrap_or_else(|| "member".to_string());
373
+
374
+ Ok(NapiOrgMember {
375
+ member_did,
376
+ role: role_str,
377
+ capabilities_json: caps_json,
378
+ issuer_did: revocation.issuer.to_string(),
379
+ attestation_rid: revocation.rid.to_string(),
380
+ revoked: true,
381
+ expires_at: revocation.expires_at.map(|e| e.to_rfc3339()),
382
+ })
383
+ }
384
+
385
+ #[napi]
386
+ pub fn list_org_members(
387
+ org_did: String,
388
+ include_revoked: bool,
389
+ repo_path: String,
390
+ ) -> napi::Result<String> {
391
+ let repo = resolve_repo(&repo_path);
392
+ let org_prefix = extract_org_prefix(&org_did);
393
+
394
+ let backend = GitRegistryBackend::from_config_unchecked(RegistryConfig::single_tenant(&repo));
395
+
396
+ let filter = MemberFilter::default();
397
+
398
+ let members = backend
399
+ .list_org_members(&org_prefix, &filter)
400
+ .map_err(|e| format_error("AUTHS_ORG_ERROR", e))?;
401
+
402
+ let result: Vec<serde_json::Value> = members
403
+ .iter()
404
+ .filter_map(|m| {
405
+ let is_revoked = m.revoked_at.is_some();
406
+ if !include_revoked && is_revoked {
407
+ return None;
408
+ }
409
+
410
+ let caps: Vec<String> = m
411
+ .capabilities
412
+ .iter()
413
+ .map(|c| c.as_str().to_string())
414
+ .collect();
415
+ let role_str = m.role.as_ref().map(|r| r.as_str()).unwrap_or("member");
416
+
417
+ Some(serde_json::json!({
418
+ "member_did": m.did.to_string(),
419
+ "role": role_str,
420
+ "capabilities": caps,
421
+ "issuer_did": m.issuer.to_string(),
422
+ "attestation_rid": m.rid.to_string(),
423
+ "revoked": is_revoked,
424
+ "expires_at": m.expires_at.map(|e| e.to_rfc3339()),
425
+ }))
426
+ })
427
+ .collect();
428
+
429
+ serde_json::to_string(&result).map_err(|e| format_error("AUTHS_ORG_ERROR", e))
430
+ }