use std::fs;
use std::path::{Path, PathBuf};
use anyhow::{Context, Result};
pub const TOKEN_BYTES: usize = 32;
pub const TOKEN_FILE: &str = "omnid.token";
pub fn load_or_create(root: &Path) -> Result<String> {
let path = token_path(root);
if let Ok(existing) = fs::read_to_string(&path) {
let trimmed = existing.trim().to_string();
if trimmed.len() >= 32 {
return Ok(trimmed);
}
}
let token = generate()?;
fs::create_dir_all(root).with_context(|| format!("creating {}", root.display()))?;
let tmp = path.with_extension("token.tmp");
fs::write(&tmp, &token).with_context(|| format!("writing {}", tmp.display()))?;
fs::rename(&tmp, &path).with_context(|| format!("renaming into {}", path.display()))?;
restrict(&path);
Ok(token)
}
pub fn token_path(root: &Path) -> PathBuf {
root.join(TOKEN_FILE)
}
fn generate() -> Result<String> {
let mut buf = [0u8; TOKEN_BYTES];
getrandom::getrandom(&mut buf).context("reading OS entropy for the daemon token")?;
Ok(buf.iter().map(|b| format!("{b:02x}")).collect())
}
#[cfg(unix)]
fn restrict(path: &Path) {
use std::os::unix::fs::PermissionsExt;
let _ = fs::set_permissions(path, fs::Permissions::from_mode(0o600));
}
#[cfg(not(unix))]
fn restrict(_path: &Path) {}
pub fn secret_eq(a: &str, b: &str) -> bool {
let (a, b) = (a.as_bytes(), b.as_bytes());
let mut diff = (a.len() ^ b.len()) as u8;
let n = a.len().max(b.len());
for i in 0..n {
let x = a.get(i).copied().unwrap_or(0);
let y = b.get(i).copied().unwrap_or(0);
diff |= x ^ y;
}
diff == 0
}
pub fn bearer(header: &str) -> &str {
let h = header.trim();
match h.strip_prefix("Bearer ").or_else(|| h.strip_prefix("bearer ")) {
Some(rest) => rest.trim(),
None => h,
}
}
pub fn host_is_local(host: Option<&str>, port: u16) -> bool {
let Some(host) = host else {
return false;
};
let host = host.trim();
let name = match host.rsplit_once(':') {
Some((n, p)) if !n.ends_with(']') || p.chars().all(|c| c.is_ascii_digit()) => {
if p.chars().all(|c| c.is_ascii_digit()) && !p.is_empty() {
let declared: u16 = p.parse().unwrap_or(0);
if declared != port {
return false;
}
}
n
}
_ => host,
};
matches!(name, "127.0.0.1" | "localhost" | "[::1]" | "::1")
}
#[cfg(test)]
mod tests {
use super::*;
fn scratch() -> PathBuf {
let p = std::env::temp_dir().join(format!(
"scema-omni-auth-{}-{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos()
));
fs::create_dir_all(&p).unwrap();
p
}
#[test]
fn a_token_is_generated_once_and_then_reused() {
let dir = scratch();
let a = load_or_create(&dir).unwrap();
let b = load_or_create(&dir).unwrap();
assert_eq!(a, b);
assert_eq!(a.len(), TOKEN_BYTES * 2);
fs::remove_dir_all(&dir).ok();
}
#[test]
fn two_daemons_do_not_share_a_token() {
let (d1, d2) = (scratch(), scratch());
assert_ne!(load_or_create(&d1).unwrap(), load_or_create(&d2).unwrap());
fs::remove_dir_all(&d1).ok();
fs::remove_dir_all(&d2).ok();
}
#[test]
fn a_truncated_token_file_is_replaced_not_honoured() {
let dir = scratch();
fs::write(token_path(&dir), "abc").unwrap();
let t = load_or_create(&dir).unwrap();
assert_eq!(t.len(), TOKEN_BYTES * 2, "a short secret nobody chose must not be installed");
fs::remove_dir_all(&dir).ok();
}
#[test]
fn secret_comparison_matches_only_the_exact_token() {
assert!(secret_eq("abc", "abc"));
assert!(!secret_eq("abc", "abd"));
assert!(!secret_eq("abc", "abcd"), "a prefix must not authenticate");
assert!(!secret_eq("abcd", "abc"));
assert!(!secret_eq("", "a"));
assert!(secret_eq("", ""));
}
#[test]
fn bearer_accepts_both_the_prefixed_and_the_bare_form() {
assert_eq!(bearer("Bearer deadbeef"), "deadbeef");
assert_eq!(bearer("bearer deadbeef"), "deadbeef");
assert_eq!(bearer(" deadbeef "), "deadbeef");
}
#[test]
fn a_rebinding_host_is_rejected() {
assert!(!host_is_local(Some("evil.example:7842"), 7842));
assert!(!host_is_local(Some("attacker.test"), 7842));
assert!(!host_is_local(None, 7842), "HTTP/1.1 requires a Host header");
}
#[test]
fn the_daemons_own_names_are_accepted() {
assert!(host_is_local(Some("127.0.0.1:7842"), 7842));
assert!(host_is_local(Some("localhost:7842"), 7842));
assert!(host_is_local(Some("127.0.0.1"), 7842));
assert!(host_is_local(Some("[::1]:7842"), 7842));
}
#[test]
fn a_local_name_on_the_wrong_port_is_rejected() {
assert!(!host_is_local(Some("localhost:3000"), 7842));
}
}