sos_migrate/authenticator/
export.rs

1use 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
14/// Export an authenticator vault to a zip archive.
15///
16/// The access point for the vault must be unlocked.
17pub async fn export_authenticator(
18    path: impl AsRef<Path>,
19    source: &AccessPoint,
20    include_qr_codes: bool,
21) -> Result<()> {
22    // Gather TOTP secrets
23    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    // Write the JSON otpauth: URLs
36    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}