This commit is contained in:
Alex 2026-07-25 22:59:25 +02:00
commit 009173a97d
Signed by: alex
SSH key fingerprint: SHA256:D1+Ub8o0v4K5y1JNivW8IxEOelqLSvPmUzBbDIoZkRQ
49 changed files with 1788 additions and 389 deletions

89
iota-terms/src/consent.rs Normal file
View file

@ -0,0 +1,89 @@
//! Durable, deployment-scoped consent records.
//!
//! This deliberately contains no UI code. Both the terminal client and the
//! daemon use the same record so a UI-local decision can never start services.
use crate::{Doc, TermsType as Type};
use std::fs;
use std::io;
use std::path::Path;
use std::time::{SystemTime, UNIX_EPOCH};
const FILE_NAME: &str = "terms-consent-v1";
#[derive(Clone, Debug, Default)]
pub struct ConsentRecord {
pub eula: Option<(String, String)>,
pub tos: Option<(String, String)>,
pub privacy: Option<(String, String)>,
}
impl ConsentRecord {
pub fn has_all_required(&self) -> bool {
self.eula.is_some() && self.tos.is_some() && self.privacy.is_some()
}
pub fn accepts(&self, eula: &Doc, tos: &Doc, privacy: &Doc) -> bool {
matches_doc(&self.eula, eula)
&& matches_doc(&self.tos, tos)
&& matches_doc(&self.privacy, privacy)
}
pub fn accept(&mut self, doc: &Doc) {
let value = Some((doc.get_version(), doc.get_hash()));
match doc.doc_type {
Type::EULA => self.eula = value,
Type::TOS => self.tos = value,
Type::PP => self.privacy = value,
}
}
}
fn matches_doc(value: &Option<(String, String)>, doc: &Doc) -> bool {
matches!(value, Some((version, hash)) if version == &doc.get_version() && hash == &doc.get_hash())
}
pub fn load(state_dir: &Path) -> ConsentRecord {
let Ok(text) = fs::read_to_string(state_dir.join(FILE_NAME)) else {
return ConsentRecord::default();
};
let mut record = ConsentRecord::default();
for line in text.lines() {
let Some((key, value)) = line.split_once('=') else {
continue;
};
let Some((version, hash)) = value.split_once(':') else {
continue;
};
let value = Some((version.to_owned(), hash.to_owned()));
match key {
"eula" => record.eula = value,
"tos" => record.tos = value,
"privacy" => record.privacy = value,
_ => {}
}
}
record
}
pub fn save(state_dir: &Path, record: &ConsentRecord) -> io::Result<()> {
fs::create_dir_all(state_dir)?;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default()
.as_secs();
let mut text = format!("# Iota terms consent record; accepted_at_unix={timestamp}\n");
for (name, value) in [
("eula", &record.eula),
("tos", &record.tos),
("privacy", &record.privacy),
] {
if let Some((version, hash)) = value {
text.push_str(&format!("{name}={version}:{hash}\n"));
}
}
let path = state_dir.join(FILE_NAME);
let temporary = state_dir.join(format!(".{FILE_NAME}.{}.tmp", std::process::id()));
fs::write(&temporary, text)?;
fs::rename(temporary, path)
}

View file

@ -1,3 +1,4 @@
pub mod consent;
pub mod terms_getter;
pub use terms_getter::Type as TermsType;