@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/device.rs ADDED
@@ -0,0 +1,212 @@
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::context::AuthsContext;
7
+ use auths_sdk::device::extend_device;
8
+ use auths_sdk::device::{link_device, revoke_device};
9
+ use auths_sdk::types::{DeviceExtensionConfig, DeviceLinkConfig};
10
+ use auths_storage::git::{
11
+ GitRegistryBackend, RegistryAttestationStorage, RegistryConfig, RegistryIdentityStorage,
12
+ };
13
+ use auths_verifier::clock::SystemClock;
14
+ use auths_verifier::core::Capability;
15
+ use auths_verifier::types::DeviceDID;
16
+ use napi_derive::napi;
17
+
18
+ use crate::error::format_error;
19
+ use crate::helpers::{make_env_config, resolve_key_alias, resolve_passphrase};
20
+ use crate::types::{NapiExtensionResult, NapiLinkResult};
21
+
22
+ fn open_backend(repo: &PathBuf) -> napi::Result<Arc<GitRegistryBackend>> {
23
+ let config = RegistryConfig::single_tenant(repo);
24
+ let backend = GitRegistryBackend::open_existing(config).map_err(|e| {
25
+ format_error(
26
+ "AUTHS_REGISTRY_ERROR",
27
+ format!("Failed to open registry: {e}"),
28
+ )
29
+ })?;
30
+ Ok(Arc::new(backend))
31
+ }
32
+
33
+ #[napi]
34
+ pub fn link_device_to_identity(
35
+ identity_key_alias: String,
36
+ capabilities: Vec<String>,
37
+ repo_path: String,
38
+ passphrase: Option<String>,
39
+ expires_in: Option<i64>,
40
+ ) -> napi::Result<NapiLinkResult> {
41
+ let passphrase_str = resolve_passphrase(passphrase);
42
+ let env_config = make_env_config(&passphrase_str, &repo_path);
43
+ let provider = Arc::new(PrefilledPassphraseProvider::new(&passphrase_str));
44
+ let clock = Arc::new(SystemClock);
45
+
46
+ let repo = PathBuf::from(shellexpand::tilde(&repo_path).as_ref());
47
+ let backend = open_backend(&repo)?;
48
+
49
+ let keychain = get_platform_keychain_with_config(&env_config)
50
+ .map_err(|e| format_error("AUTHS_KEYCHAIN_ERROR", format!("Keychain error: {e}")))?;
51
+
52
+ let alias = resolve_key_alias(&identity_key_alias, keychain.as_ref())?;
53
+
54
+ let parsed_caps: Vec<Capability> = capabilities
55
+ .iter()
56
+ .map(|c| {
57
+ Capability::parse(c).map_err(|e| {
58
+ format_error(
59
+ "AUTHS_INVALID_INPUT",
60
+ format!("Invalid capability '{c}': {e}"),
61
+ )
62
+ })
63
+ })
64
+ .collect::<napi::Result<Vec<_>>>()?;
65
+
66
+ let link_config = DeviceLinkConfig {
67
+ identity_key_alias: alias,
68
+ device_key_alias: None,
69
+ device_did: None,
70
+ capabilities: parsed_caps,
71
+ expires_in: expires_in.map(|s| s as u64),
72
+ note: None,
73
+ payload: None,
74
+ };
75
+
76
+ let keychain: Arc<dyn auths_core::storage::keychain::KeyStorage + Send + Sync> =
77
+ Arc::from(keychain);
78
+ let identity_storage = Arc::new(RegistryIdentityStorage::new(&repo));
79
+ let attestation_storage = Arc::new(RegistryAttestationStorage::new(&repo));
80
+
81
+ let ctx = AuthsContext::builder()
82
+ .registry(backend)
83
+ .key_storage(keychain)
84
+ .clock(clock.clone())
85
+ .identity_storage(identity_storage)
86
+ .attestation_sink(attestation_storage.clone())
87
+ .attestation_source(attestation_storage)
88
+ .passphrase_provider(provider)
89
+ .build();
90
+
91
+ let result = link_device(link_config, &ctx, clock.as_ref())
92
+ .map_err(|e| format_error("AUTHS_DEVICE_ERROR", format!("Device linking failed: {e}")))?;
93
+
94
+ Ok(NapiLinkResult {
95
+ device_did: result.device_did.to_string(),
96
+ attestation_id: result.attestation_id.to_string(),
97
+ })
98
+ }
99
+
100
+ #[napi]
101
+ pub fn revoke_device_from_identity(
102
+ device_did: String,
103
+ identity_key_alias: String,
104
+ repo_path: String,
105
+ passphrase: Option<String>,
106
+ note: Option<String>,
107
+ ) -> napi::Result<()> {
108
+ let passphrase_str = resolve_passphrase(passphrase);
109
+ let env_config = make_env_config(&passphrase_str, &repo_path);
110
+ let provider = Arc::new(PrefilledPassphraseProvider::new(&passphrase_str));
111
+ let clock = Arc::new(SystemClock);
112
+
113
+ let repo = PathBuf::from(shellexpand::tilde(&repo_path).as_ref());
114
+ let backend = open_backend(&repo)?;
115
+
116
+ let keychain = get_platform_keychain_with_config(&env_config)
117
+ .map_err(|e| format_error("AUTHS_KEYCHAIN_ERROR", format!("Keychain error: {e}")))?;
118
+
119
+ let alias = resolve_key_alias(&identity_key_alias, keychain.as_ref())?;
120
+
121
+ let keychain: Arc<dyn auths_core::storage::keychain::KeyStorage + Send + Sync> =
122
+ Arc::from(keychain);
123
+ let identity_storage = Arc::new(RegistryIdentityStorage::new(&repo));
124
+ let attestation_storage = Arc::new(RegistryAttestationStorage::new(&repo));
125
+
126
+ let ctx = AuthsContext::builder()
127
+ .registry(backend)
128
+ .key_storage(keychain)
129
+ .clock(clock.clone())
130
+ .identity_storage(identity_storage)
131
+ .attestation_sink(attestation_storage.clone())
132
+ .attestation_source(attestation_storage)
133
+ .passphrase_provider(provider)
134
+ .build();
135
+
136
+ revoke_device(&device_did, &alias, &ctx, note, clock.as_ref()).map_err(|e| {
137
+ format_error(
138
+ "AUTHS_DEVICE_ERROR",
139
+ format!("Device revocation failed: {e}"),
140
+ )
141
+ })?;
142
+
143
+ Ok(())
144
+ }
145
+
146
+ #[napi]
147
+ pub fn extend_device_authorization(
148
+ device_did: String,
149
+ identity_key_alias: String,
150
+ expires_in: i64,
151
+ repo_path: String,
152
+ passphrase: Option<String>,
153
+ ) -> napi::Result<NapiExtensionResult> {
154
+ if expires_in <= 0 {
155
+ return Err(format_error(
156
+ "AUTHS_INVALID_INPUT",
157
+ "expires_in must be positive (> 0)",
158
+ ));
159
+ }
160
+
161
+ let passphrase_str = resolve_passphrase(passphrase);
162
+ let env_config = make_env_config(&passphrase_str, &repo_path);
163
+ let provider = Arc::new(PrefilledPassphraseProvider::new(&passphrase_str));
164
+ let clock = Arc::new(SystemClock);
165
+
166
+ let repo = PathBuf::from(shellexpand::tilde(&repo_path).as_ref());
167
+ let backend = open_backend(&repo)?;
168
+
169
+ let keychain = get_platform_keychain_with_config(&env_config)
170
+ .map_err(|e| format_error("AUTHS_KEYCHAIN_ERROR", format!("Keychain error: {e}")))?;
171
+ let keychain: Arc<dyn auths_core::storage::keychain::KeyStorage + Send + Sync> =
172
+ Arc::from(keychain);
173
+
174
+ let identity_storage = Arc::new(RegistryIdentityStorage::new(&repo));
175
+ let attestation_storage = Arc::new(RegistryAttestationStorage::new(&repo));
176
+
177
+ let alias = resolve_key_alias(&identity_key_alias, keychain.as_ref())?;
178
+
179
+ let ext_config = DeviceExtensionConfig {
180
+ repo_path: repo,
181
+ device_did: DeviceDID::parse(&device_did)
182
+ .map_err(|e| format_error("AUTHS_INVALID_INPUT", e))?,
183
+ expires_in: expires_in as u64,
184
+ identity_key_alias: alias,
185
+ device_key_alias: None,
186
+ };
187
+
188
+ let ctx = AuthsContext::builder()
189
+ .registry(backend)
190
+ .key_storage(keychain)
191
+ .clock(clock.clone())
192
+ .identity_storage(identity_storage)
193
+ .attestation_sink(attestation_storage.clone())
194
+ .attestation_source(attestation_storage)
195
+ .passphrase_provider(provider)
196
+ .build();
197
+
198
+ let result = extend_device(ext_config, &ctx, clock.as_ref()).map_err(|e| {
199
+ format_error(
200
+ "AUTHS_DEVICE_ERROR",
201
+ format!("Device extension failed: {e}"),
202
+ )
203
+ })?;
204
+
205
+ Ok(NapiExtensionResult {
206
+ device_did: result.device_did.to_string(),
207
+ new_expires_at: result.new_expires_at.to_rfc3339(),
208
+ previous_expires_at: result
209
+ .previous_expires_at
210
+ .map(|t: chrono::DateTime<chrono::Utc>| t.to_rfc3339()),
211
+ })
212
+ }
@@ -0,0 +1,106 @@
1
+ use std::process::Command;
2
+
3
+ use auths_sdk::ports::diagnostics::{
4
+ CheckCategory, CheckResult, CryptoDiagnosticProvider, DiagnosticError, GitDiagnosticProvider,
5
+ };
6
+ use auths_sdk::workflows::diagnostics::DiagnosticsWorkflow;
7
+ use napi_derive::napi;
8
+
9
+ use crate::error::format_error;
10
+
11
+ struct FfiDiagnosticAdapter;
12
+
13
+ impl GitDiagnosticProvider for FfiDiagnosticAdapter {
14
+ fn check_git_version(&self) -> Result<CheckResult, DiagnosticError> {
15
+ let output = Command::new("git").arg("--version").output();
16
+ let (passed, message) = match output {
17
+ Ok(out) if out.status.success() => {
18
+ let version = String::from_utf8_lossy(&out.stdout).trim().to_string();
19
+ (true, Some(version))
20
+ }
21
+ _ => (false, Some("git command not found on PATH".to_string())),
22
+ };
23
+ Ok(CheckResult {
24
+ name: "Git installed".to_string(),
25
+ passed,
26
+ message,
27
+ config_issues: vec![],
28
+ category: CheckCategory::Advisory,
29
+ })
30
+ }
31
+
32
+ fn get_git_config(&self, key: &str) -> Result<Option<String>, DiagnosticError> {
33
+ let output = Command::new("git")
34
+ .args(["config", "--global", "--get", key])
35
+ .output()
36
+ .map_err(|e| DiagnosticError::ExecutionFailed(e.to_string()))?;
37
+
38
+ if output.status.success() {
39
+ Ok(String::from_utf8(output.stdout)
40
+ .ok()
41
+ .map(|s| s.trim().to_string()))
42
+ } else {
43
+ Ok(None)
44
+ }
45
+ }
46
+ }
47
+
48
+ impl CryptoDiagnosticProvider for FfiDiagnosticAdapter {
49
+ fn check_ssh_keygen_available(&self) -> Result<CheckResult, DiagnosticError> {
50
+ let output = Command::new("ssh-keygen").arg("-V").output();
51
+ let (passed, message) = match output {
52
+ Ok(out) if out.status.success() => (true, Some("ssh-keygen found on PATH".to_string())),
53
+ _ => (
54
+ false,
55
+ Some("ssh-keygen command not found on PATH".to_string()),
56
+ ),
57
+ };
58
+ Ok(CheckResult {
59
+ name: "ssh-keygen installed".to_string(),
60
+ passed,
61
+ message,
62
+ config_issues: vec![],
63
+ category: CheckCategory::Advisory,
64
+ })
65
+ }
66
+ }
67
+
68
+ #[napi]
69
+ pub fn run_diagnostics(repo_path: String, passphrase: Option<String>) -> napi::Result<String> {
70
+ let _repo = repo_path;
71
+ let _passphrase = passphrase;
72
+
73
+ let adapter = FfiDiagnosticAdapter;
74
+ let workflow = DiagnosticsWorkflow::new(&adapter, &adapter);
75
+ let report = workflow
76
+ .run()
77
+ .map_err(|e| format_error("AUTHS_DIAGNOSTIC_ERROR", e))?;
78
+
79
+ let all_passed = report.checks.iter().all(|c| c.passed);
80
+
81
+ let checks: Vec<serde_json::Value> = report
82
+ .checks
83
+ .iter()
84
+ .map(|c| {
85
+ let fix_hint = if !c.passed {
86
+ Some("Run: auths init --profile developer")
87
+ } else {
88
+ None
89
+ };
90
+ serde_json::json!({
91
+ "name": c.name,
92
+ "passed": c.passed,
93
+ "message": c.message,
94
+ "fix_hint": fix_hint,
95
+ })
96
+ })
97
+ .collect();
98
+
99
+ let result = serde_json::json!({
100
+ "checks": checks,
101
+ "all_passed": all_passed,
102
+ "version": env!("CARGO_PKG_VERSION"),
103
+ });
104
+
105
+ serde_json::to_string(&result).map_err(|e| format_error("AUTHS_DIAGNOSTIC_ERROR", e))
106
+ }
package/src/error.rs ADDED
@@ -0,0 +1,5 @@
1
+ use napi::Status;
2
+
3
+ pub fn format_error(code: &str, message: impl std::fmt::Display) -> napi::Error {
4
+ napi::Error::new(Status::GenericFailure, format!("[{code}] {message}"))
5
+ }
package/src/helpers.rs ADDED
@@ -0,0 +1,60 @@
1
+ use std::path::PathBuf;
2
+
3
+ use auths_core::config::{EnvironmentConfig, KeychainConfig};
4
+ use auths_core::storage::keychain::{
5
+ IdentityDID, KeyAlias, KeyRole, KeyStorage, get_platform_keychain_with_config,
6
+ };
7
+
8
+ use crate::error::format_error;
9
+
10
+ #[allow(clippy::disallowed_methods)] // Presentation boundary: env var read is intentional
11
+ pub fn resolve_passphrase(passphrase: Option<String>) -> String {
12
+ passphrase.unwrap_or_else(|| std::env::var("AUTHS_PASSPHRASE").unwrap_or_default())
13
+ }
14
+
15
+ #[allow(clippy::disallowed_methods)] // Presentation boundary: env var read is intentional
16
+ pub fn resolve_repo_path(path: Option<String>) -> PathBuf {
17
+ let raw = path
18
+ .unwrap_or_else(|| std::env::var("AUTHS_HOME").unwrap_or_else(|_| "~/.auths".to_string()));
19
+ let expanded = shellexpand::tilde(&raw);
20
+ PathBuf::from(expanded.as_ref())
21
+ }
22
+
23
+ pub fn make_env_config(passphrase: &str, repo_path: &str) -> EnvironmentConfig {
24
+ let mut keychain = KeychainConfig::from_env();
25
+ if keychain.backend.is_none() {
26
+ keychain.backend = Some("file".to_string());
27
+ }
28
+ keychain.passphrase = Some(passphrase.to_string());
29
+ EnvironmentConfig {
30
+ auths_home: Some(repo_path.into()),
31
+ keychain,
32
+ ssh_agent_socket: None,
33
+ }
34
+ }
35
+
36
+ pub fn get_keychain(config: &EnvironmentConfig) -> napi::Result<Box<dyn KeyStorage + Send + Sync>> {
37
+ get_platform_keychain_with_config(config).map_err(|e| format_error("AUTHS_KEYCHAIN_ERROR", e))
38
+ }
39
+
40
+ pub fn resolve_key_alias(
41
+ identity_ref: &str,
42
+ keychain: &(dyn KeyStorage + Send + Sync),
43
+ ) -> napi::Result<KeyAlias> {
44
+ if identity_ref.starts_with("did:") {
45
+ let did =
46
+ IdentityDID::parse(identity_ref).map_err(|e| format_error("AUTHS_INVALID_INPUT", e))?;
47
+ let aliases = keychain
48
+ .list_aliases_for_identity_with_role(&did, KeyRole::Primary)
49
+ .map_err(|e| format_error("AUTHS_KEY_NOT_FOUND", format!("Key lookup failed: {e}")))?;
50
+ aliases.into_iter().next().ok_or_else(|| {
51
+ format_error(
52
+ "AUTHS_KEY_NOT_FOUND",
53
+ format!("No primary key found for identity '{identity_ref}'"),
54
+ )
55
+ })
56
+ } else {
57
+ KeyAlias::new(identity_ref)
58
+ .map_err(|e| format_error("AUTHS_KEY_NOT_FOUND", format!("Invalid key alias: {e}")))
59
+ }
60
+ }