1use clap::Args;
2use lux_lib::{
3 config::Config, package::PackageName, remote_package_db::RemotePackageDB,
4 upload::ProjectUpload, workspace::Workspace,
5};
6
7use miette::{IntoDiagnostic, Result};
8
9#[cfg(feature = "gpgme")]
10use lux_lib::upload::SignatureProtocol;
11
12#[derive(Args)]
13pub struct Upload {
14 #[cfg(feature = "gpgme")]
16 #[arg(long, default_value_t)]
17 sign_protocol: SignatureProtocol,
18
19 #[arg(short, long, visible_short_alias = 'p')]
21 pub(crate) package: Option<PackageName>,
22
23 #[arg(short, long, visible_short_alias = 'c')]
27 tfa_code: Option<String>,
28}
29
30#[cfg(feature = "gpgme")]
31pub async fn upload(data: Upload, config: Config) -> Result<()> {
32 let workspace = Workspace::current_or_err()?;
33
34 let package_db = RemotePackageDB::from_config(&config).await?;
35 let tfa_code = tfa_code_from_args_or_secret(&data, &config)?;
36 if let Some(package) = data.package {
37 let project = workspace.select_member(&package)?;
38 ProjectUpload::new()
39 .project(project)
40 .config(&config)
41 .sign_protocol(data.sign_protocol.clone())
42 .maybe_tfa_code(tfa_code)
43 .package_db(&package_db)
44 .upload_to_luarocks()
45 .await?;
46 } else {
47 for project in workspace.members() {
48 ProjectUpload::new()
49 .project(project)
50 .config(&config)
51 .sign_protocol(data.sign_protocol.clone())
52 .maybe_tfa_code(tfa_code.clone())
53 .package_db(&package_db)
54 .upload_to_luarocks()
55 .await?;
56 }
57 }
58
59 Ok(())
60}
61
62#[cfg(not(feature = "gpgme"))]
63pub async fn upload(data: Upload, config: Config) -> Result<()> {
64 let workspace = Workspace::current_or_err()?;
65 let package_db = RemotePackageDB::from_config(&config).await?;
66 let tfa_code = tfa_code_from_args_or_secret(&data, &config)?;
67 if let Some(package) = data.package {
68 let project = workspace.select_member(&package)?;
69 ProjectUpload::new()
70 .project(project)
71 .config(&config)
72 .maybe_tfa_code(tfa_code)
73 .package_db(&package_db)
74 .upload_to_luarocks()
75 .await?;
76 } else {
77 for project in workspace.members() {
78 ProjectUpload::new()
79 .project(project)
80 .config(&config)
81 .maybe_tfa_code(tfa_code.clone())
82 .package_db(&package_db)
83 .upload_to_luarocks()
84 .await?;
85 }
86 }
87
88 Ok(())
89}
90
91fn tfa_code_from_args_or_secret(data: &Upload, config: &Config) -> Result<Option<String>> {
92 match &data.tfa_code {
93 Some(code) => Ok(Some(code.to_owned())),
94 None => match std::env::var("LUAROCKS_2FA_SECRET") {
95 Ok(secret) => {
96 let secret = base32::decode(base32::Alphabet::Crockford, &secret)
97 .ok_or(totp_rs::SecretParseError::ParseBase32)
98 .into_diagnostic()?;
99 let totp = totp_rs::Builder::new()
100 .with_algorithm(totp_rs::Algorithm::SHA1)
101 .with_digits(6)
102 .with_skew(1)
103 .with_step_duration(30)
104 .with_secret(secret)
105 .build()
106 .into_diagnostic()?;
107 Ok(Some(totp.generate_current().to_string()))
108 }
109 Err(_) => {
110 if config.no_prompt() || config.no_tfa() {
111 Ok(None)
112 } else {
113 let code = inquire::Password::new("Enter your 2FA code:")
114 .without_confirmation()
115 .prompt()
116 .into_diagnostic()?;
117 Ok(Some(code))
118 }
119 }
120 },
121 }
122}