Skip to main content

onepassword_async/
wrappers.rs

1use crate::invoke;
2use onepassword_shared::types::{ClientConfig, Invocation, InvocationParameters, Item, Vault};
3use onepassword_sys::Error as FfiError;
4use secrecy::SecretString;
5use std::{ops::Deref, sync::Arc};
6
7type FfiResult<T> = Result<T, FfiError>;
8
9#[derive(Clone)]
10pub struct Client(Arc<ClientInner>);
11
12impl Deref for Client {
13    type Target = ClientInner;
14
15    fn deref(&self) -> &Self::Target {
16        &self.0
17    }
18}
19pub struct ClientInner {
20    pub(crate) id: u64,
21}
22
23impl Drop for ClientInner {
24    fn drop(&mut self) {
25        let id_str = self.id.to_string();
26        onepassword_sys::free_client(&id_str);
27    }
28}
29
30impl Client {
31    pub async fn new(config: ClientConfig) -> FfiResult<Client> {
32        let client = Arc::new(ClientInner {
33            id: Self::get_client_id(config).await?,
34        });
35
36        Ok(Client(client))
37    }
38
39    async fn get_client_id(config: ClientConfig) -> FfiResult<u64> {
40        onepassword_sys::validate_checksums();
41
42        let serialized_config = serde_json::to_string(&config).unwrap();
43        let id_buffer = onepassword_sys::get_client_id_buffer(&serialized_config).await?;
44
45        Ok(id_buffer.to_string().parse().unwrap())
46    }
47}
48
49impl Client {
50    pub async fn vaults(&self) -> FfiResult<Vec<VaultWrapper>> {
51        let vaults: Vec<Vault> = invoke(Invocation {
52            client_id: self.id,
53            parameters: InvocationParameters::VaultsList { _marker: () },
54        })
55        .await?;
56
57        let wrapped_vaults = vaults
58            .into_iter()
59            .map(|vault| VaultWrapper {
60                vault,
61                client: self.clone(),
62            })
63            .collect();
64
65        Ok(wrapped_vaults)
66    }
67
68    pub async fn get_vault_by_title(&self, title: &str) -> FfiResult<Option<VaultWrapper>> {
69        let vault = self.vaults().await?.into_iter().find(|v| v.title == title);
70        Ok(vault)
71    }
72}
73
74pub struct VaultWrapper {
75    pub vault: Vault,
76    client: Client,
77}
78
79impl std::fmt::Debug for VaultWrapper {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        f.debug_struct("VaultWrapper")
82            .field("vault", &self.vault)
83            .finish()
84    }
85}
86
87impl Deref for VaultWrapper {
88    type Target = Vault;
89
90    fn deref(&self) -> &Self::Target {
91        &self.vault
92    }
93}
94
95impl VaultWrapper {
96    pub async fn items(&self) -> FfiResult<Vec<ItemWrapper>> {
97        let items = invoke::<Vec<Item>>(Invocation {
98            client_id: self.client.id,
99            parameters: InvocationParameters::ItemsList {
100                vault_id: self.vault.id.clone(),
101                filters: vec![],
102            },
103        })
104        .await?;
105
106        let items = items
107            .into_iter()
108            .map(|item| ItemWrapper {
109                item,
110                client: self.client.clone(),
111                vault_id: self.vault.id.clone(),
112            })
113            .collect();
114
115        Ok(items)
116    }
117
118    pub async fn items_for_website(&self, website: &str) -> FfiResult<Vec<ItemWrapper>> {
119        let trim_protocol = !website.contains("://");
120        let items = self
121            .items()
122            .await?
123            .into_iter()
124            .filter(|it| {
125                it.websites.iter().any(|w| {
126                    if trim_protocol {
127                        w.url
128                            .split_once("://")
129                            .is_some_and(|(_, url)| website.starts_with(url))
130                    } else {
131                        website.starts_with(&w.url)
132                    }
133                })
134            })
135            .collect();
136
137        Ok(items)
138    }
139}
140
141pub struct ItemWrapper {
142    pub item: Item,
143    client: Client,
144    vault_id: String,
145}
146
147impl std::fmt::Debug for ItemWrapper {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        f.debug_struct("ItemWrapper")
150            .field("item", &self.item)
151            .field("vault_id", &self.vault_id)
152            .finish()
153    }
154}
155
156impl Deref for ItemWrapper {
157    type Target = Item;
158
159    fn deref(&self) -> &Self::Target {
160        &self.item
161    }
162}
163
164impl ItemWrapper {
165    fn construct_secret_ref(&self, field: &str) -> String {
166        format!("op://{}/{}/{field}", self.vault_id, self.item.id)
167    }
168}
169
170impl ItemWrapper {
171    pub async fn password(&self) -> FfiResult<Option<SecretString>> {
172        let secret_reference = self.construct_secret_ref("password");
173
174        let result = invoke::<SecretString>(Invocation {
175            client_id: self.client.id,
176            parameters: InvocationParameters::SecretsResolve { secret_reference },
177        })
178        .await;
179
180        match result {
181            Ok(secret) => Ok(Some(secret)),
182            Err(e) if e.code() == 133 => Ok(None),
183            Err(e) => Err(e),
184        }
185    }
186}