Skip to main content

auths_cli/commands/
index.rs

1use anyhow::{Context, Result};
2use clap::{Args, Subcommand};
3use std::path::PathBuf;
4
5use auths_id::storage::layout::{self, StorageLayoutConfig};
6use auths_index::{AttestationIndex, rebuild_attestations_from_git};
7
8#[derive(Args, Debug, Clone)]
9#[command(about = "Manage the device authorization index for fast lookups.")]
10pub struct IndexCommand {
11    #[command(subcommand)]
12    pub command: IndexSubcommand,
13}
14
15#[derive(Subcommand, Debug, Clone)]
16pub enum IndexSubcommand {
17    /// Rebuild the index from Git refs.
18    Rebuild,
19
20    /// Show index statistics.
21    Stats,
22
23    /// Query authorizations by device identity ID.
24    #[command(name = "query-device")]
25    QueryDevice {
26        /// The device identity ID to query.
27        #[arg(long)]
28        device_did: String,
29    },
30
31    /// Query authorizations by issuer identity ID.
32    #[command(name = "query-issuer")]
33    QueryIssuer {
34        /// The issuer identity ID to query.
35        #[arg(long)]
36        issuer_did: String,
37    },
38}
39
40/// Handles the `index` subcommand.
41pub fn handle_index(
42    cmd: IndexCommand,
43    repo_opt: Option<PathBuf>,
44    attestation_prefix_override: Option<String>,
45    attestation_blob_name_override: Option<String>,
46) -> Result<()> {
47    let repo_path = layout::resolve_repo_path(repo_opt)?;
48    let index_path = repo_path.join(".auths-index.db");
49
50    let mut config = StorageLayoutConfig::default();
51    if let Some(prefix) = attestation_prefix_override {
52        config.device_attestation_prefix = prefix.into();
53    }
54    if let Some(blob_name) = attestation_blob_name_override {
55        config.attestation_blob_name = blob_name.into();
56    }
57
58    match cmd.command {
59        IndexSubcommand::Rebuild => {
60            println!("Rebuilding device authorization index...");
61            println!("   Repository: {:?}", repo_path);
62            println!("   Index path: {:?}", index_path);
63            println!(
64                "   Authorization prefix: {}",
65                config.device_attestation_prefix
66            );
67
68            let index = AttestationIndex::open_or_create(&index_path)
69                .context("Failed to open or create index")?;
70
71            let stats = rebuild_attestations_from_git(
72                &index,
73                &repo_path,
74                &config.device_attestation_prefix,
75                &config.attestation_blob_name,
76            )
77            .context("Failed to rebuild index from Git")?;
78
79            println!("\nRebuild complete:");
80            println!("   Refs scanned: {}", stats.refs_scanned);
81            println!("   Attestations indexed: {}", stats.attestations_indexed);
82            if stats.errors > 0 {
83                println!("   Errors: {}", stats.errors);
84            }
85
86            Ok(())
87        }
88
89        IndexSubcommand::Stats => {
90            if !index_path.exists() {
91                println!("No index found at {:?}", index_path);
92                println!("Run 'auths index rebuild' to create one.");
93                return Ok(());
94            }
95
96            let index =
97                AttestationIndex::open_or_create(&index_path).context("Failed to open index")?;
98
99            let stats = index.stats().context("Failed to get index stats")?;
100
101            println!("Attestation Index Statistics");
102            println!("   Index path: {:?}", index_path);
103            println!();
104            println!("   Total attestations:  {}", stats.total_attestations);
105            println!("   Active attestations: {}", stats.active_attestations);
106            println!("   Revoked attestations: {}", stats.revoked_attestations);
107            println!("   With expiry:         {}", stats.with_expiry);
108            println!("   Unique devices:      {}", stats.unique_devices);
109            println!("   Unique issuers:      {}", stats.unique_issuers);
110
111            Ok(())
112        }
113
114        IndexSubcommand::QueryDevice { device_did } => {
115            if !index_path.exists() {
116                println!("No index found at {:?}", index_path);
117                println!("Run 'auths index rebuild' to create one.");
118                return Ok(());
119            }
120
121            let index =
122                AttestationIndex::open_or_create(&index_path).context("Failed to open index")?;
123
124            let results = index
125                .query_by_device(&device_did)
126                .context("Failed to query by device")?;
127
128            if results.is_empty() {
129                println!("No attestations found for device: {}", device_did);
130            } else {
131                println!(
132                    "Found {} attestation(s) for device {}:",
133                    results.len(),
134                    device_did
135                );
136                for att in results {
137                    println!();
138                    println!("   RID: {}", att.rid);
139                    println!("   Issuer: {}", att.issuer_did);
140                    println!("   Revoked At: {:?}", att.revoked_at);
141                    if let Some(expires) = att.expires_at {
142                        println!("   Expires: {}", expires);
143                    }
144                    println!("   Git ref: {}", att.git_ref);
145                }
146            }
147
148            Ok(())
149        }
150
151        IndexSubcommand::QueryIssuer { issuer_did } => {
152            if !index_path.exists() {
153                println!("No index found at {:?}", index_path);
154                println!("Run 'auths index rebuild' to create one.");
155                return Ok(());
156            }
157
158            let index =
159                AttestationIndex::open_or_create(&index_path).context("Failed to open index")?;
160
161            let results = index
162                .query_by_issuer(&issuer_did)
163                .context("Failed to query by issuer")?;
164
165            if results.is_empty() {
166                println!("No attestations found for issuer: {}", issuer_did);
167            } else {
168                println!(
169                    "Found {} attestation(s) for issuer {}:",
170                    results.len(),
171                    issuer_did
172                );
173                for att in results {
174                    println!();
175                    println!("   RID: {}", att.rid);
176                    println!("   Device: {}", att.device_did);
177                    println!("   Revoked At: {:?}", att.revoked_at);
178                    if let Some(expires) = att.expires_at {
179                        println!("   Expires: {}", expires);
180                    }
181                    println!("   Git ref: {}", att.git_ref);
182                }
183            }
184
185            Ok(())
186        }
187    }
188}
189
190impl crate::commands::executable::ExecutableCommand for IndexCommand {
191    fn execute(&self, ctx: &crate::config::CliConfig) -> anyhow::Result<()> {
192        handle_index(self.clone(), ctx.repo_path.clone(), None, None)
193    }
194}