smart-tree 8.0.1

Smart Tree - An intelligent, AI-friendly directory visualization tool
Documentation
//! Identity Resolution - Who are you?
//!
//! Supports multiple identity providers:
//! - i1.is - Our identity service (primary)
//! - GitHub - OAuth + SSH keys
//! - Local - Machine users (for standalone)

use anyhow::Result;
use serde::{Deserialize, Serialize};
use std::fmt;

/// Identity provider
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub enum IdentityProvider {
    /// i1.is identity service
    I1is,
    /// GitHub OAuth
    GitHub,
    /// Local machine user
    Local,
}

/// A user identity
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct Identity {
    /// The provider
    pub provider: IdentityProvider,
    /// Username/identifier
    pub username: String,
    /// Optional display name
    pub display_name: Option<String>,
    /// Public key (for verification)
    pub public_key: Option<String>,
}

impl Identity {
    /// Create an i1.is identity
    pub fn i1is(username: &str) -> Self {
        Identity {
            provider: IdentityProvider::I1is,
            username: username.to_string(),
            display_name: None,
            public_key: None,
        }
    }

    /// Create a GitHub identity
    pub fn github(username: &str) -> Self {
        Identity {
            provider: IdentityProvider::GitHub,
            username: username.to_string(),
            display_name: None,
            public_key: None,
        }
    }

    /// Create a local identity
    pub fn local(username: &str) -> Self {
        Identity {
            provider: IdentityProvider::Local,
            username: username.to_string(),
            display_name: None,
            public_key: None,
        }
    }

    /// Parse an identity string
    ///
    /// Formats:
    /// - "user@i1.is" -> i1.is identity
    /// - "github:user" -> GitHub identity
    /// - "user" -> Local identity (fallback)
    pub fn parse(s: &str) -> Result<Self> {
        if s.ends_with("@i1.is") {
            let username = s.trim_end_matches("@i1.is");
            Ok(Identity::i1is(username))
        } else if s.starts_with("github:") {
            let username = s.trim_start_matches("github:");
            Ok(Identity::github(username))
        } else if s.contains('@') {
            // Assume i1.is format: user@i1.is
            let username = s.split('@').next().unwrap_or(s);
            Ok(Identity::i1is(username))
        } else {
            // Local user
            Ok(Identity::local(s))
        }
    }

    /// Get the canonical string representation
    pub fn canonical(&self) -> String {
        match self.provider {
            IdentityProvider::I1is => format!("{}@i1.is", self.username),
            IdentityProvider::GitHub => format!("github:{}", self.username),
            IdentityProvider::Local => format!("local:{}", self.username),
        }
    }

    /// Check if this is an AI identity
    pub fn is_ai(&self) -> bool {
        // AI identities typically have special prefixes
        self.username.starts_with("claude-")
            || self.username.starts_with("ai-")
            || self.username.starts_with("assistant-")
    }
}

impl fmt::Display for Identity {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.canonical())
    }
}

/// Resolve an identity from a provider
pub struct IdentityResolver;

impl IdentityResolver {
    /// Resolve an i1.is identity (fetch public key, display name, etc.)
    pub async fn resolve_i1is(username: &str) -> Result<Identity> {
        // TODO: Call i1.is API to get user info
        // For now, return basic identity
        Ok(Identity {
            provider: IdentityProvider::I1is,
            username: username.to_string(),
            display_name: None,
            public_key: None,
        })
    }

    /// Resolve a GitHub identity via API
    pub async fn resolve_github(username: &str) -> Result<Identity> {
        // TODO: Call GitHub API to get user info + SSH keys
        // For now, return basic identity
        Ok(Identity {
            provider: IdentityProvider::GitHub,
            username: username.to_string(),
            display_name: None,
            public_key: None,
        })
    }

    /// Get current user's identity from environment
    pub fn current() -> Result<Identity> {
        // Check for i1.is identity first
        if let Ok(id) = std::env::var("I1IS_USER") {
            return Ok(Identity::i1is(&id));
        }

        // Check for GitHub user
        if let Ok(user) = std::env::var("GITHUB_USER") {
            return Ok(Identity::github(&user));
        }

        // Fall back to local user
        let username = whoami::username();
        Ok(Identity::local(&username))
    }

    /// Verify an identity signature
    pub fn verify(_identity: &Identity, _message: &[u8], _signature: &[u8]) -> Result<bool> {
        // TODO: Implement signature verification using public key
        Ok(true)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_identity() {
        let i1 = Identity::parse("hue@i1.is").unwrap();
        assert_eq!(i1.provider, IdentityProvider::I1is);
        assert_eq!(i1.username, "hue");

        let gh = Identity::parse("github:8b-is").unwrap();
        assert_eq!(gh.provider, IdentityProvider::GitHub);
        assert_eq!(gh.username, "8b-is");

        let local = Identity::parse("alice").unwrap();
        assert_eq!(local.provider, IdentityProvider::Local);
        assert_eq!(local.username, "alice");
    }

    #[test]
    fn test_canonical() {
        assert_eq!(Identity::i1is("hue").canonical(), "hue@i1.is");
        assert_eq!(Identity::github("8b-is").canonical(), "github:8b-is");
        assert_eq!(Identity::local("alice").canonical(), "local:alice");
    }
}