🔥 Okibi

yuki / okibi

焚き火の仲間だけの自前Gitホスティング(このサイト自身)

git clone https://okibi.fly.dev/yuki/okibi.git

okibi / src / util.rs

use rand::RngCore;
use sha2::{Digest, Sha256};
use std::time::{SystemTime, UNIX_EPOCH};

/// Current unix time in seconds.
pub fn now() -> i64 {
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .unwrap()
        .as_secs() as i64
}

/// 32 random bytes as lowercase hex (64 chars).
pub fn random_token() -> String {
    let mut buf = [0u8; 32];
    rand::thread_rng().fill_bytes(&mut buf);
    hex::encode(buf)
}

/// SHA-256 hex digest — we store only hashes of secrets (sessions, PATs, magic links).
pub fn sha256_hex(input: &str) -> String {
    let mut h = Sha256::new();
    h.update(input.as_bytes());
    hex::encode(h.finalize())
}

/// Normalize an email for comparison (trim + lowercase).
pub fn norm_email(s: &str) -> String {
    s.trim().to_lowercase()
}

/// Derive a default handle from an email local-part, kept to [a-z0-9-].
pub fn handle_from_email(email: &str) -> String {
    let local = email.split('@').next().unwrap_or("user");
    let mut out = String::new();
    for c in local.chars() {
        if c.is_ascii_alphanumeric() {
            out.push(c.to_ascii_lowercase());
        } else if c == '.' || c == '_' || c == '-' || c == '+' {
            out.push('-');
        }
    }
    let trimmed = out.trim_matches('-').to_string();
    if trimmed.is_empty() {
        "user".into()
    } else {
        trimmed
    }
}

/// Validate a repo/handle name: lowercase alnum, dash, underscore, dot. 1..=64 chars.
pub fn valid_name(s: &str) -> bool {
    !s.is_empty()
        && s.len() <= 64
        && s.chars()
            .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_' || c == '.')
        && s != "."
        && s != ".."
}