@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
@@ -0,0 +1,217 @@
1
+ use std::io::Read;
2
+ use std::path::PathBuf;
3
+ use std::sync::Arc;
4
+
5
+ use auths_core::signing::PrefilledPassphraseProvider;
6
+ use auths_core::storage::keychain::{KeyAlias, get_platform_keychain_with_config};
7
+ use auths_sdk::context::AuthsContext;
8
+ use auths_sdk::ports::artifact::{ArtifactDigest, ArtifactError, ArtifactMetadata, ArtifactSource};
9
+ use auths_sdk::signing::{
10
+ ArtifactSigningParams, SigningKeyMaterial, sign_artifact as sdk_sign_artifact,
11
+ };
12
+ use auths_storage::git::{
13
+ GitRegistryBackend, RegistryAttestationStorage, RegistryConfig, RegistryIdentityStorage,
14
+ };
15
+ use auths_verifier::clock::SystemClock;
16
+ use napi_derive::napi;
17
+ use sha2::{Digest, Sha256};
18
+
19
+ use crate::error::format_error;
20
+ use crate::helpers::{make_env_config, resolve_passphrase};
21
+
22
+ struct FileArtifact {
23
+ path: PathBuf,
24
+ }
25
+
26
+ impl ArtifactSource for FileArtifact {
27
+ fn digest(&self) -> Result<ArtifactDigest, ArtifactError> {
28
+ let mut file = std::fs::File::open(&self.path)
29
+ .map_err(|e| ArtifactError::Io(format!("{}: {e}", self.path.display())))?;
30
+ let mut hasher = Sha256::new();
31
+ let mut buf = [0u8; 8192];
32
+ loop {
33
+ let n = file
34
+ .read(&mut buf)
35
+ .map_err(|e| ArtifactError::Io(e.to_string()))?;
36
+ if n == 0 {
37
+ break;
38
+ }
39
+ hasher.update(&buf[..n]);
40
+ }
41
+ Ok(ArtifactDigest {
42
+ algorithm: "sha256".to_string(),
43
+ hex: hex::encode(hasher.finalize()),
44
+ })
45
+ }
46
+
47
+ fn metadata(&self) -> Result<ArtifactMetadata, ArtifactError> {
48
+ let digest = self.digest()?;
49
+ let meta = std::fs::metadata(&self.path)
50
+ .map_err(|e| ArtifactError::Metadata(format!("{}: {e}", self.path.display())))?;
51
+ Ok(ArtifactMetadata {
52
+ artifact_type: "file".to_string(),
53
+ digest,
54
+ name: self
55
+ .path
56
+ .file_name()
57
+ .map(|n| n.to_string_lossy().to_string()),
58
+ size: Some(meta.len()),
59
+ })
60
+ }
61
+ }
62
+
63
+ struct BytesArtifact {
64
+ data: Vec<u8>,
65
+ }
66
+
67
+ impl ArtifactSource for BytesArtifact {
68
+ fn digest(&self) -> Result<ArtifactDigest, ArtifactError> {
69
+ let mut hasher = Sha256::new();
70
+ hasher.update(&self.data);
71
+ Ok(ArtifactDigest {
72
+ algorithm: "sha256".to_string(),
73
+ hex: hex::encode(hasher.finalize()),
74
+ })
75
+ }
76
+
77
+ fn metadata(&self) -> Result<ArtifactMetadata, ArtifactError> {
78
+ let digest = self.digest()?;
79
+ Ok(ArtifactMetadata {
80
+ artifact_type: "bytes".to_string(),
81
+ digest,
82
+ name: None,
83
+ size: Some(self.data.len() as u64),
84
+ })
85
+ }
86
+ }
87
+
88
+ #[napi(object)]
89
+ #[derive(Clone)]
90
+ pub struct NapiArtifactResult {
91
+ pub attestation_json: String,
92
+ pub rid: String,
93
+ pub digest: String,
94
+ pub file_size: i64,
95
+ }
96
+
97
+ fn build_context_and_sign(
98
+ artifact: Arc<dyn ArtifactSource>,
99
+ identity_key_alias: &str,
100
+ repo_path: &str,
101
+ passphrase: Option<String>,
102
+ expires_in: Option<i64>,
103
+ note: Option<String>,
104
+ ) -> napi::Result<NapiArtifactResult> {
105
+ let passphrase_str = resolve_passphrase(passphrase);
106
+ let env_config = make_env_config(&passphrase_str, repo_path);
107
+ let provider = Arc::new(PrefilledPassphraseProvider::new(&passphrase_str));
108
+ let clock = Arc::new(SystemClock);
109
+
110
+ let repo = PathBuf::from(shellexpand::tilde(repo_path).as_ref());
111
+ let config = RegistryConfig::single_tenant(&repo);
112
+ let backend = Arc::new(GitRegistryBackend::open_existing(config).map_err(|e| {
113
+ format_error(
114
+ "AUTHS_REGISTRY_ERROR",
115
+ format!("Failed to open registry: {e}"),
116
+ )
117
+ })?);
118
+
119
+ let keychain = get_platform_keychain_with_config(&env_config)
120
+ .map_err(|e| format_error("AUTHS_KEYCHAIN_ERROR", format!("Keychain error: {e}")))?;
121
+ let keychain = Arc::from(keychain);
122
+
123
+ let identity_storage = Arc::new(RegistryIdentityStorage::new(&repo));
124
+ let attestation_storage = Arc::new(RegistryAttestationStorage::new(&repo));
125
+
126
+ let alias = KeyAlias::new(identity_key_alias)
127
+ .map_err(|e| format_error("AUTHS_KEY_NOT_FOUND", format!("Invalid key alias: {e}")))?;
128
+
129
+ let ctx = AuthsContext::builder()
130
+ .registry(backend)
131
+ .key_storage(keychain)
132
+ .clock(clock)
133
+ .identity_storage(identity_storage)
134
+ .attestation_sink(attestation_storage.clone())
135
+ .attestation_source(attestation_storage)
136
+ .passphrase_provider(provider)
137
+ .build();
138
+
139
+ let file_size = artifact
140
+ .metadata()
141
+ .map(|m| m.size.unwrap_or(0))
142
+ .unwrap_or(0) as i64;
143
+
144
+ let params = ArtifactSigningParams {
145
+ artifact,
146
+ identity_key: Some(SigningKeyMaterial::Alias(alias.clone())),
147
+ device_key: SigningKeyMaterial::Alias(alias),
148
+ expires_in: expires_in.map(|s| s as u64),
149
+ note,
150
+ };
151
+
152
+ let result = sdk_sign_artifact(params, &ctx).map_err(|e| {
153
+ format_error(
154
+ "AUTHS_SIGNING_FAILED",
155
+ format!("Artifact signing failed: {e}"),
156
+ )
157
+ })?;
158
+
159
+ Ok(NapiArtifactResult {
160
+ attestation_json: result.attestation_json,
161
+ rid: result.rid.to_string(),
162
+ digest: result.digest,
163
+ file_size,
164
+ })
165
+ }
166
+
167
+ #[napi]
168
+ pub fn sign_artifact(
169
+ file_path: String,
170
+ identity_key_alias: String,
171
+ repo_path: String,
172
+ passphrase: Option<String>,
173
+ expires_in: Option<i64>,
174
+ note: Option<String>,
175
+ ) -> napi::Result<NapiArtifactResult> {
176
+ let path = PathBuf::from(shellexpand::tilde(&file_path).as_ref());
177
+ if !path.exists() {
178
+ return Err(format_error(
179
+ "AUTHS_INVALID_INPUT",
180
+ format!(
181
+ "Artifact not found: '{file_path}'. Check the path and ensure the file exists."
182
+ ),
183
+ ));
184
+ }
185
+
186
+ let artifact = Arc::new(FileArtifact { path });
187
+ build_context_and_sign(
188
+ artifact,
189
+ &identity_key_alias,
190
+ &repo_path,
191
+ passphrase,
192
+ expires_in,
193
+ note,
194
+ )
195
+ }
196
+
197
+ #[napi]
198
+ pub fn sign_artifact_bytes(
199
+ data: napi::bindgen_prelude::Buffer,
200
+ identity_key_alias: String,
201
+ repo_path: String,
202
+ passphrase: Option<String>,
203
+ expires_in: Option<i64>,
204
+ note: Option<String>,
205
+ ) -> napi::Result<NapiArtifactResult> {
206
+ let artifact = Arc::new(BytesArtifact {
207
+ data: data.to_vec(),
208
+ });
209
+ build_context_and_sign(
210
+ artifact,
211
+ &identity_key_alias,
212
+ &repo_path,
213
+ passphrase,
214
+ expires_in,
215
+ note,
216
+ )
217
+ }
@@ -0,0 +1,104 @@
1
+ use std::path::PathBuf;
2
+ use std::sync::Arc;
3
+
4
+ use auths_id::attestation::group::AttestationGroup;
5
+ use auths_id::storage::attestation::AttestationSource;
6
+ use auths_storage::git::{GitRegistryBackend, RegistryAttestationStorage, RegistryConfig};
7
+ use auths_verifier::core::Attestation;
8
+ use auths_verifier::types::DeviceDID;
9
+ use napi_derive::napi;
10
+
11
+ use crate::error::format_error;
12
+
13
+ #[napi(object)]
14
+ #[derive(Clone)]
15
+ pub struct NapiAttestation {
16
+ pub rid: String,
17
+ pub issuer: String,
18
+ pub subject: String,
19
+ pub device_did: String,
20
+ pub capabilities: Vec<String>,
21
+ pub signer_type: Option<String>,
22
+ pub expires_at: Option<String>,
23
+ pub revoked_at: Option<String>,
24
+ pub created_at: Option<String>,
25
+ pub delegated_by: Option<String>,
26
+ pub json: String,
27
+ }
28
+
29
+ fn attestation_to_napi(att: &Attestation) -> NapiAttestation {
30
+ let json = serde_json::to_string(att).unwrap_or_default();
31
+ NapiAttestation {
32
+ rid: att.rid.to_string(),
33
+ issuer: att.issuer.to_string(),
34
+ subject: att.subject.to_string(),
35
+ device_did: att.subject.to_string(),
36
+ capabilities: att.capabilities.iter().map(|c| c.to_string()).collect(),
37
+ signer_type: att.signer_type.as_ref().map(|s| format!("{s:?}")),
38
+ expires_at: att.expires_at.map(|t| t.to_rfc3339()),
39
+ revoked_at: att.revoked_at.map(|t| t.to_rfc3339()),
40
+ created_at: att.timestamp.map(|t| t.to_rfc3339()),
41
+ delegated_by: att.delegated_by.as_ref().map(|d| d.to_string()),
42
+ json,
43
+ }
44
+ }
45
+
46
+ fn open_attestation_storage(repo_path: &str) -> napi::Result<Arc<RegistryAttestationStorage>> {
47
+ let repo = PathBuf::from(shellexpand::tilde(repo_path).as_ref());
48
+ let config = RegistryConfig::single_tenant(&repo);
49
+ let _backend = GitRegistryBackend::open_existing(config).map_err(|e| {
50
+ format_error(
51
+ "AUTHS_REGISTRY_ERROR",
52
+ format!("Failed to open registry: {e}"),
53
+ )
54
+ })?;
55
+ Ok(Arc::new(RegistryAttestationStorage::new(&repo)))
56
+ }
57
+
58
+ #[napi]
59
+ pub fn list_attestations(repo_path: String) -> napi::Result<Vec<NapiAttestation>> {
60
+ let storage = open_attestation_storage(&repo_path)?;
61
+ let all = storage.load_all_attestations().map_err(|e| {
62
+ format_error(
63
+ "AUTHS_REGISTRY_ERROR",
64
+ format!("Failed to load attestations: {e}"),
65
+ )
66
+ })?;
67
+ Ok(all.iter().map(attestation_to_napi).collect())
68
+ }
69
+
70
+ #[napi]
71
+ pub fn list_attestations_by_device(
72
+ repo_path: String,
73
+ device_did: String,
74
+ ) -> napi::Result<Vec<NapiAttestation>> {
75
+ let storage = open_attestation_storage(&repo_path)?;
76
+ let all = storage.load_all_attestations().map_err(|e| {
77
+ format_error(
78
+ "AUTHS_REGISTRY_ERROR",
79
+ format!("Failed to load attestations: {e}"),
80
+ )
81
+ })?;
82
+ let group = AttestationGroup::from_list(all);
83
+ Ok(group
84
+ .get(&device_did)
85
+ .map(|atts| atts.iter().map(attestation_to_napi).collect())
86
+ .unwrap_or_default())
87
+ }
88
+
89
+ #[napi]
90
+ pub fn get_latest_attestation(
91
+ repo_path: String,
92
+ device_did: String,
93
+ ) -> napi::Result<Option<NapiAttestation>> {
94
+ let storage = open_attestation_storage(&repo_path)?;
95
+ let all = storage.load_all_attestations().map_err(|e| {
96
+ format_error(
97
+ "AUTHS_REGISTRY_ERROR",
98
+ format!("Failed to load attestations: {e}"),
99
+ )
100
+ })?;
101
+ let group = AttestationGroup::from_list(all);
102
+ let did = DeviceDID::parse(&device_did).map_err(|e| format_error("AUTHS_INVALID_INPUT", e))?;
103
+ Ok(group.latest(&did).map(attestation_to_napi))
104
+ }
package/src/audit.rs ADDED
@@ -0,0 +1,128 @@
1
+ use std::path::PathBuf;
2
+
3
+ use auths_infra_git::audit::Git2LogProvider;
4
+ use auths_sdk::ports::git::SignatureStatus;
5
+ use auths_sdk::workflows::audit::AuditWorkflow;
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 parse_timestamp(ts: &str) -> Option<chrono::NaiveDateTime> {
15
+ chrono::NaiveDateTime::parse_from_str(&ts[..19], "%Y-%m-%dT%H:%M:%S").ok()
16
+ }
17
+
18
+ #[napi]
19
+ pub fn generate_audit_report(
20
+ target_repo_path: String,
21
+ auths_repo_path: String,
22
+ since: Option<String>,
23
+ until: Option<String>,
24
+ author: Option<String>,
25
+ limit: Option<u32>,
26
+ ) -> napi::Result<String> {
27
+ let target = resolve_repo(&target_repo_path);
28
+ let _auths = resolve_repo(&auths_repo_path);
29
+ let limit = limit.unwrap_or(500) as usize;
30
+
31
+ let provider =
32
+ Git2LogProvider::open(&target).map_err(|e| format_error("AUTHS_AUDIT_ERROR", e))?;
33
+
34
+ let workflow = AuditWorkflow::new(&provider);
35
+ let report = workflow
36
+ .generate_report(None, Some(limit))
37
+ .map_err(|e| format_error("AUTHS_AUDIT_ERROR", e))?;
38
+
39
+ let since_filter = since.and_then(|s| {
40
+ chrono::NaiveDate::parse_from_str(&s, "%Y-%m-%d")
41
+ .ok()
42
+ .and_then(|d| d.and_hms_opt(0, 0, 0))
43
+ });
44
+ let until_filter = until.and_then(|u| {
45
+ chrono::NaiveDate::parse_from_str(&u, "%Y-%m-%d")
46
+ .ok()
47
+ .and_then(|d| d.and_hms_opt(23, 59, 59))
48
+ });
49
+
50
+ let commits: Vec<serde_json::Value> = report
51
+ .commits
52
+ .iter()
53
+ .filter(|c| {
54
+ if author.as_ref().is_some_and(|a| c.author_email != *a) {
55
+ return false;
56
+ }
57
+ if since_filter.is_some_and(|since_dt| {
58
+ parse_timestamp(&c.timestamp).is_some_and(|ct| ct < since_dt)
59
+ }) {
60
+ return false;
61
+ }
62
+ if until_filter.is_some_and(|until_dt| {
63
+ parse_timestamp(&c.timestamp).is_some_and(|ct| ct > until_dt)
64
+ }) {
65
+ return false;
66
+ }
67
+ true
68
+ })
69
+ .map(|c| {
70
+ let (sig_type, signer_did, verified) = match &c.signature_status {
71
+ SignatureStatus::AuthsSigned { signer_did } => {
72
+ (Some("auths"), Some(signer_did.as_str()), Some(true))
73
+ }
74
+ SignatureStatus::SshSigned => (Some("ssh"), None, None),
75
+ SignatureStatus::GpgSigned { verified } => (Some("gpg"), None, Some(*verified)),
76
+ SignatureStatus::InvalidSignature { .. } => (Some("invalid"), None, Some(false)),
77
+ SignatureStatus::Unsigned => (None, None, None),
78
+ };
79
+ serde_json::json!({
80
+ "oid": c.hash,
81
+ "author_name": c.author_name,
82
+ "author_email": c.author_email,
83
+ "date": c.timestamp,
84
+ "message": c.message,
85
+ "signature_type": sig_type,
86
+ "signer_did": signer_did,
87
+ "verified": verified,
88
+ })
89
+ })
90
+ .collect();
91
+
92
+ let total = commits.len();
93
+ let signed = commits
94
+ .iter()
95
+ .filter(|c| c["signature_type"] != serde_json::Value::Null)
96
+ .count();
97
+ let unsigned = total - signed;
98
+ let auths_signed = commits
99
+ .iter()
100
+ .filter(|c| c["signature_type"] == "auths")
101
+ .count();
102
+ let gpg_signed = commits
103
+ .iter()
104
+ .filter(|c| c["signature_type"] == "gpg")
105
+ .count();
106
+ let ssh_signed = commits
107
+ .iter()
108
+ .filter(|c| c["signature_type"] == "ssh")
109
+ .count();
110
+ let verification_passed = commits.iter().filter(|c| c["verified"] == true).count();
111
+ let verification_failed = signed - verification_passed;
112
+
113
+ let result = serde_json::json!({
114
+ "commits": commits,
115
+ "summary": {
116
+ "total_commits": total,
117
+ "signed_commits": signed,
118
+ "unsigned_commits": unsigned,
119
+ "auths_signed": auths_signed,
120
+ "gpg_signed": gpg_signed,
121
+ "ssh_signed": ssh_signed,
122
+ "verification_passed": verification_passed,
123
+ "verification_failed": verification_failed,
124
+ },
125
+ });
126
+
127
+ serde_json::to_string(&result).map_err(|e| format_error("AUTHS_AUDIT_ERROR", e))
128
+ }
@@ -0,0 +1,63 @@
1
+ use std::path::PathBuf;
2
+ use std::sync::Arc;
3
+
4
+ use auths_core::signing::PrefilledPassphraseProvider;
5
+ use auths_core::storage::keychain::get_platform_keychain_with_config;
6
+ use auths_sdk::workflows::signing::{
7
+ CommitSigningContext, CommitSigningParams, CommitSigningWorkflow,
8
+ };
9
+ use napi_derive::napi;
10
+
11
+ use crate::error::format_error;
12
+ use crate::helpers::{make_env_config, resolve_passphrase};
13
+
14
+ #[napi(object)]
15
+ #[derive(Clone)]
16
+ pub struct NapiCommitSignPemResult {
17
+ pub signature_pem: String,
18
+ pub method: String,
19
+ pub namespace: String,
20
+ }
21
+
22
+ #[napi]
23
+ pub fn sign_commit(
24
+ data: napi::bindgen_prelude::Buffer,
25
+ identity_key_alias: String,
26
+ repo_path: String,
27
+ passphrase: Option<String>,
28
+ ) -> napi::Result<NapiCommitSignPemResult> {
29
+ let passphrase_str = resolve_passphrase(passphrase);
30
+ let env_config = make_env_config(&passphrase_str, &repo_path);
31
+ let provider = Arc::new(PrefilledPassphraseProvider::new(&passphrase_str));
32
+
33
+ let keychain = get_platform_keychain_with_config(&env_config)
34
+ .map_err(|e| format_error("AUTHS_KEYCHAIN_ERROR", format!("Keychain error: {e}")))?;
35
+ let keychain = Arc::from(keychain);
36
+
37
+ let repo = PathBuf::from(shellexpand::tilde(&repo_path).as_ref());
38
+
39
+ let params =
40
+ CommitSigningParams::new(&identity_key_alias, "git", data.to_vec()).with_repo_path(repo);
41
+
42
+ let signing_ctx = CommitSigningContext {
43
+ key_storage: keychain,
44
+ passphrase_provider: provider,
45
+ agent_signing: Arc::new(auths_sdk::ports::agent::NoopAgentProvider),
46
+ };
47
+
48
+ #[allow(clippy::disallowed_methods)] // Presentation boundary
49
+ let now = chrono::Utc::now();
50
+
51
+ let pem = CommitSigningWorkflow::execute(&signing_ctx, params, now).map_err(|e| {
52
+ format_error(
53
+ "AUTHS_SIGNING_FAILED",
54
+ format!("Commit signing failed: {e}"),
55
+ )
56
+ })?;
57
+
58
+ Ok(NapiCommitSignPemResult {
59
+ signature_pem: pem,
60
+ method: "direct".to_string(),
61
+ namespace: "git".to_string(),
62
+ })
63
+ }