mirror of
https://github.com/Twigpine/node.git
synced 2026-11-09 05:20:05 -10:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad3c070e78 | ||
|
|
915a3fe637 | ||
|
|
f0d93fdfba |
No files matched your search
Generated
+1
@@ -3600,6 +3600,7 @@ dependencies = [
|
||||
"ed25519-dalek 2.2.0",
|
||||
"gitlawb-core",
|
||||
"icaptcha-client",
|
||||
"libc",
|
||||
"mockito",
|
||||
"percent-encoding",
|
||||
"reqwest",
|
||||
|
||||
@@ -29,6 +29,7 @@ uuid = { workspace = true }
|
||||
url = { workspace = true }
|
||||
percent-encoding = { workspace = true }
|
||||
urlencoding = "2"
|
||||
libc = "0.2"
|
||||
alloy = { version = "1", default-features = false, features = [
|
||||
"contract",
|
||||
"provider-http",
|
||||
|
||||
@@ -131,23 +131,13 @@ async fn cmd_new_with_reader(
|
||||
}
|
||||
}
|
||||
|
||||
fs::create_dir_all(&dir)
|
||||
crate::secret_file::create_dir(&dir)
|
||||
.with_context(|| format!("failed to create directory {}", dir.display()))?;
|
||||
|
||||
let keypair = Keypair::generate();
|
||||
let pem = keypair.to_pem()?;
|
||||
|
||||
// Write with restricted permissions
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
fs::write(&path, pem.as_bytes())?;
|
||||
fs::set_permissions(&path, fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
fs::write(&path, pem.as_bytes())?;
|
||||
}
|
||||
crate::secret_file::write(&path, pem.as_bytes())?;
|
||||
|
||||
let did = keypair.did();
|
||||
println!("✓ Generated new identity");
|
||||
@@ -202,16 +192,7 @@ async fn cmd_backup(out: Option<PathBuf>, dir: Option<PathBuf>) -> Result<()> {
|
||||
.join("identity.pem.bak")
|
||||
});
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
fs::write(&dest, pem.as_bytes())?;
|
||||
fs::set_permissions(&dest, fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
fs::write(&dest, pem.as_bytes())?;
|
||||
}
|
||||
crate::secret_file::write(&dest, pem.as_bytes())?;
|
||||
|
||||
println!("✓ Identity backed up");
|
||||
println!(" DID: {}", keypair.did());
|
||||
@@ -262,19 +243,10 @@ async fn cmd_restore_with_reader(
|
||||
}
|
||||
}
|
||||
|
||||
fs::create_dir_all(&base)
|
||||
crate::secret_file::create_dir(&base)
|
||||
.with_context(|| format!("failed to create directory {}", base.display()))?;
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
fs::write(&dest, pem.as_bytes())?;
|
||||
fs::set_permissions(&dest, fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
fs::write(&dest, pem.as_bytes())?;
|
||||
}
|
||||
crate::secret_file::write(&dest, pem.as_bytes())?;
|
||||
|
||||
println!("✓ Identity restored");
|
||||
println!(" DID: {}", keypair.did());
|
||||
|
||||
+6
-15
@@ -109,16 +109,16 @@ pub async fn run(args: InitArgs) -> Result<()> {
|
||||
.dir
|
||||
.clone()
|
||||
.unwrap_or_else(|| dirs::home_dir().unwrap_or_default().join(".gitlawb"));
|
||||
std::fs::create_dir_all(&ucan_dir)?;
|
||||
crate::secret_file::create_dir(&ucan_dir)?;
|
||||
let record = json!({
|
||||
"ucan": ucan,
|
||||
"node": args.node,
|
||||
"did": did.to_string(),
|
||||
"saved_at": chrono::Utc::now().to_rfc3339(),
|
||||
});
|
||||
std::fs::write(
|
||||
ucan_dir.join("ucan.json"),
|
||||
serde_json::to_string_pretty(&record)?,
|
||||
crate::secret_file::write(
|
||||
&ucan_dir.join("ucan.json"),
|
||||
serde_json::to_string_pretty(&record)?.as_bytes(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -232,22 +232,13 @@ fn generate_identity(dir: Option<&std::path::Path>) -> Result<gitlawb_core::iden
|
||||
.context("could not determine home directory")?
|
||||
.join(".gitlawb")
|
||||
};
|
||||
std::fs::create_dir_all(&base)?;
|
||||
crate::secret_file::create_dir(&base)?;
|
||||
|
||||
let keypair = gitlawb_core::identity::Keypair::generate();
|
||||
let pem = keypair.to_pem()?;
|
||||
let path = base.join("identity.pem");
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::write(&path, pem.as_bytes())?;
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
std::fs::write(&path, pem.as_bytes())?;
|
||||
}
|
||||
crate::secret_file::write(&path, pem.as_bytes())?;
|
||||
|
||||
Ok(keypair)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ mod protect;
|
||||
mod quickstart;
|
||||
mod register;
|
||||
mod repo;
|
||||
mod secret_file;
|
||||
mod star;
|
||||
mod status;
|
||||
mod sync;
|
||||
|
||||
@@ -100,14 +100,17 @@ pub async fn run(args: QuickstartArgs) -> Result<()> {
|
||||
let payload: Value = resp.json().await.unwrap_or_default();
|
||||
let ucan = payload["ucan"].as_str().unwrap_or("");
|
||||
if !ucan.is_empty() {
|
||||
std::fs::create_dir_all(&dir)?;
|
||||
crate::secret_file::create_dir(&dir)?;
|
||||
let record = json!({
|
||||
"ucan": ucan,
|
||||
"node": args.node,
|
||||
"did": did,
|
||||
"saved_at": chrono::Utc::now().to_rfc3339(),
|
||||
});
|
||||
std::fs::write(&ucan_path, serde_json::to_string_pretty(&record)?)?;
|
||||
crate::secret_file::write(
|
||||
&ucan_path,
|
||||
serde_json::to_string_pretty(&record)?.as_bytes(),
|
||||
)?;
|
||||
}
|
||||
let trust = payload["trust_score"].as_f64().unwrap_or(0.0);
|
||||
println!(" ✓ Registered successfully");
|
||||
@@ -226,23 +229,15 @@ pub async fn run(args: QuickstartArgs) -> Result<()> {
|
||||
|
||||
// ── Helpers ───────────────────────────────────────────────────────────────
|
||||
|
||||
fn generate_identity(dir: &PathBuf) -> Result<gitlawb_core::identity::Keypair> {
|
||||
std::fs::create_dir_all(dir).with_context(|| format!("failed to create {}", dir.display()))?;
|
||||
fn generate_identity(dir: &std::path::Path) -> Result<gitlawb_core::identity::Keypair> {
|
||||
crate::secret_file::create_dir(dir)
|
||||
.with_context(|| format!("failed to create {}", dir.display()))?;
|
||||
|
||||
let keypair = gitlawb_core::identity::Keypair::generate();
|
||||
let pem = keypair.to_pem()?;
|
||||
let path = dir.join("identity.pem");
|
||||
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::write(&path, pem.as_bytes())?;
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600))?;
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
std::fs::write(&path, pem.as_bytes())?;
|
||||
}
|
||||
crate::secret_file::write(&path, pem.as_bytes())?;
|
||||
|
||||
let did = keypair.did();
|
||||
println!(" ✓ Generated new identity");
|
||||
|
||||
@@ -72,7 +72,10 @@ pub async fn run(args: RegisterArgs) -> Result<()> {
|
||||
"did": did.to_string(),
|
||||
"saved_at": chrono::Utc::now().to_rfc3339(),
|
||||
});
|
||||
std::fs::write(&ucan_path, serde_json::to_string_pretty(&record)?)?;
|
||||
crate::secret_file::write(
|
||||
&ucan_path,
|
||||
serde_json::to_string_pretty(&record)?.as_bytes(),
|
||||
)?;
|
||||
tracing::debug!("saved UCAN to {}", ucan_path.display());
|
||||
}
|
||||
|
||||
@@ -108,7 +111,7 @@ fn ucan_path(dir: Option<&std::path::Path>) -> Result<PathBuf> {
|
||||
.context("could not determine home directory")?
|
||||
.join(".gitlawb")
|
||||
};
|
||||
std::fs::create_dir_all(&base)?;
|
||||
crate::secret_file::create_dir(&base)?;
|
||||
Ok(base.join("ucan.json"))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
//! Writes and directories that hold key material or bearer tokens.
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::Path;
|
||||
|
||||
/// Write `contents` to `path` with owner-only permissions.
|
||||
///
|
||||
/// On unix the mode is pinned at creation, so the file never exists with a
|
||||
/// permissive mode between open and chmod. `O_NOFOLLOW` refuses to write
|
||||
/// through a pre-planted symlink. `mode()` applies only when the file is
|
||||
/// created, so the mode is pinned on the descriptor before the file is
|
||||
/// truncated: a pre-existing loose file is tightened before, not after, the
|
||||
/// new contents land in it.
|
||||
pub(crate) fn write(path: &Path, contents: &[u8]) -> std::io::Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::{OpenOptionsExt, PermissionsExt};
|
||||
let mut f = std::fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create(true)
|
||||
.mode(0o600)
|
||||
.custom_flags(libc::O_NOFOLLOW)
|
||||
.open(path)?;
|
||||
f.set_permissions(std::fs::Permissions::from_mode(0o600))?;
|
||||
f.set_len(0)?;
|
||||
f.write_all(contents)?;
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
std::fs::write(path, contents)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create `path` (and missing parents) as an owner-only directory.
|
||||
///
|
||||
/// An existing directory is also re-pinned: a `~/.gitlawb` created before
|
||||
/// this helper stays group- and world-listable otherwise. A symlinked
|
||||
/// `path` is left alone rather than chmodded through, which would strip
|
||||
/// group and world access from whatever the link points at.
|
||||
pub(crate) fn create_dir(path: &Path) -> std::io::Result<()> {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt};
|
||||
std::fs::DirBuilder::new()
|
||||
.recursive(true)
|
||||
.mode(0o700)
|
||||
.create(path)?;
|
||||
// Re-pin through the opened directory, not the path: O_NOFOLLOW
|
||||
// refuses a swapped-in symlink and fchmod acts on the checked inode,
|
||||
// so a final-component swap between check and chmod cannot retarget
|
||||
// the mode change at whatever the link points at.
|
||||
match std::fs::OpenOptions::new()
|
||||
.read(true)
|
||||
.custom_flags(libc::O_NOFOLLOW)
|
||||
.open(path)
|
||||
{
|
||||
Ok(dir) => {
|
||||
use std::os::unix::io::AsRawFd;
|
||||
if unsafe { libc::fchmod(dir.as_raw_fd(), 0o700) } != 0 {
|
||||
return Err(std::io::Error::last_os_error());
|
||||
}
|
||||
}
|
||||
// ELOOP: the path is a symlink and is left alone per contract.
|
||||
Err(e) if e.raw_os_error() == Some(libc::ELOOP) => {}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
std::fs::create_dir_all(path)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn write_creates_file_with_0600() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let dir = TempDir::new().unwrap();
|
||||
let path = dir.path().join("key.pem");
|
||||
super::write(&path, b"secret").unwrap();
|
||||
assert_eq!(std::fs::read(&path).unwrap(), b"secret");
|
||||
assert_eq!(
|
||||
std::fs::metadata(&path).unwrap().permissions().mode() & 0o777,
|
||||
0o600
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn write_tightens_preexisting_loose_file() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let dir = TempDir::new().unwrap();
|
||||
let path = dir.path().join("key.pem");
|
||||
std::fs::write(&path, b"old").unwrap();
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o644)).unwrap();
|
||||
super::write(&path, b"new").unwrap();
|
||||
assert_eq!(std::fs::read(&path).unwrap(), b"new");
|
||||
assert_eq!(
|
||||
std::fs::metadata(&path).unwrap().permissions().mode() & 0o777,
|
||||
0o600
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn write_refuses_symlink() {
|
||||
let dir = TempDir::new().unwrap();
|
||||
let target = dir.path().join("target.pem");
|
||||
std::fs::write(&target, b"planted").unwrap();
|
||||
let link = dir.path().join("link.pem");
|
||||
std::os::unix::fs::symlink(&target, &link).unwrap();
|
||||
assert!(super::write(&link, b"secret").is_err());
|
||||
assert_eq!(std::fs::read(&target).unwrap(), b"planted");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn create_dir_modes_0700_and_tightens_existing() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let dir = TempDir::new().unwrap();
|
||||
let path = dir.path().join("a").join("b");
|
||||
super::create_dir(&path).unwrap();
|
||||
assert_eq!(
|
||||
std::fs::metadata(&path).unwrap().permissions().mode() & 0o777,
|
||||
0o700
|
||||
);
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
super::create_dir(&path).unwrap();
|
||||
assert_eq!(
|
||||
std::fs::metadata(&path).unwrap().permissions().mode() & 0o777,
|
||||
0o700
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(unix)]
|
||||
fn create_dir_does_not_chmod_through_symlink() {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let dir = TempDir::new().unwrap();
|
||||
let target = dir.path().join("shared");
|
||||
std::fs::create_dir(&target).unwrap();
|
||||
std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
let link = dir.path().join("link");
|
||||
std::os::unix::fs::symlink(&target, &link).unwrap();
|
||||
super::create_dir(&link).unwrap();
|
||||
assert_eq!(
|
||||
std::fs::metadata(&target).unwrap().permissions().mode() & 0o777,
|
||||
0o755
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -90,7 +90,7 @@ async fn cmd_delegate(
|
||||
let encoded = ucan.encode()?;
|
||||
|
||||
if let Some(path) = out {
|
||||
std::fs::write(&path, &encoded)?;
|
||||
crate::secret_file::write(&path, encoded.as_bytes())?;
|
||||
println!("UCAN saved to {}", path.display());
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user