sos_migrate/authenticator/
export.rs1use super::{AuthenticatorUrls, OTP_AUTH_URLS};
2use crate::{Error, Result};
3use async_zip::{
4 tokio::write::ZipFileWriter, Compression, ZipDateTimeBuilder,
5 ZipEntryBuilder,
6};
7use sos_backend::AccessPoint;
8use sos_vault::{secret::Secret, SecretAccess};
9use sos_vfs as vfs;
10use std::{collections::HashMap, path::Path};
11use time::OffsetDateTime;
12use url::Url;
13
14pub async fn export_authenticator(
18 path: impl AsRef<Path>,
19 source: &AccessPoint,
20 include_qr_codes: bool,
21) -> Result<()> {
22 let mut totp_secrets = HashMap::new();
24 for id in source.vault().keys() {
25 if let Some((_, Secret::Totp { totp, .. }, _)) =
26 source.read_secret(id).await?
27 {
28 totp_secrets.insert(*id, totp);
29 }
30 }
31
32 let inner = vfs::File::create(path.as_ref()).await?;
33 let mut writer = ZipFileWriter::with_tokio(inner);
34
35 let mut auth_urls = AuthenticatorUrls::default();
37 for (id, totp) in &totp_secrets {
38 let url: Url = totp.get_url().parse()?;
39 auth_urls.otp.insert(*id, url);
40 }
41 let buffer = serde_json::to_vec_pretty(&auth_urls)?;
42 let entry = get_entry(OTP_AUTH_URLS)?;
43 writer.write_entry_whole(entry, &buffer).await?;
44
45 if include_qr_codes {
46 for (id, totp) in totp_secrets {
47 let name = format!("qr/{}.png", id);
48 let buffer = totp.get_qr_png().map_err(Error::Message)?;
49 let entry = get_entry(&name)?;
50 writer.write_entry_whole(entry, &buffer).await?;
51 }
52 }
53
54 writer.close().await?;
55 Ok(())
56}
57
58fn get_entry(path: &str) -> Result<ZipEntryBuilder> {
59 let now = OffsetDateTime::now_utc();
60 let (hours, minutes, seconds) = now.time().as_hms();
61 let month: u8 = now.month().into();
62
63 let dt = ZipDateTimeBuilder::new()
64 .year(now.year().into())
65 .month(month.into())
66 .day(now.day().into())
67 .hour(hours.into())
68 .minute(minutes.into())
69 .second(seconds.into())
70 .build();
71
72 Ok(ZipEntryBuilder::new(path.into(), Compression::Deflate)
73 .last_modification_date(dt))
74}