Skip to main content

bitwarden_importers/importers/onepassword/access/
wire.rs

1//! serde DTOs for every 1Password endpoint.
2//!
3//! These carry only the fields the client reads. serde ignores everything else on the wire, so the
4//! structs stay small while remaining forward compatible with the full server responses.
5
6use serde::{Deserialize, Serialize};
7
8/// The JSON "opdata" envelope as it appears on the wire.
9///
10/// It is also serialized back to the server as the request body of the encrypted POST endpoints.
11#[derive(Debug, Deserialize, Serialize)]
12pub(super) struct EncryptedEnvelope {
13    pub kid: String,
14    pub enc: String,
15    pub cty: String,
16    #[serde(default, skip_serializing_if = "Option::is_none")]
17    pub iv: Option<String>,
18    pub data: String,
19}
20
21/// An RSA private key JWK as it appears on the wire.
22///
23/// Extra JWK members (`dp`, `dq`, `qi`, `alg`, `kty`, `ext`) are ignored: the CRT values are
24/// recomputed from `p` and `q` when the key is built.
25#[derive(Deserialize)]
26pub(super) struct RsaKeyJwk {
27    pub kid: String,
28    pub e: String,
29    pub n: String,
30    pub p: String,
31    pub q: String,
32    pub d: String,
33}
34
35/// The decrypted AES key JSON.
36#[derive(Deserialize)]
37pub(super) struct AesKeyJson {
38    pub kid: String,
39    pub k: String,
40}
41
42/// The `v1/account/keysets` payload.
43#[derive(Debug, Deserialize)]
44pub(super) struct KeysetsInfo {
45    pub keysets: Vec<KeysetInfo>,
46}
47
48/// A single keyset.
49#[derive(Debug, Deserialize)]
50pub(super) struct KeysetInfo {
51    pub uuid: String,
52    #[serde(default, rename = "encryptedBy")]
53    pub encrypted_by: String,
54    pub sn: i64,
55    #[serde(rename = "encSymKey")]
56    pub enc_sym_key: KeyDerivationInfo,
57    #[serde(rename = "encPriKey")]
58    pub enc_pri_key: EncryptedEnvelope,
59}
60
61/// An encrypted symmetric key envelope that additionally carries the KDF parameters for the master
62/// keyset (`alg`/`p2s`/`p2c`).
63#[derive(Debug, Deserialize)]
64pub(super) struct KeyDerivationInfo {
65    pub kid: String,
66    pub enc: String,
67    pub cty: String,
68    #[serde(default)]
69    pub iv: Option<String>,
70    pub data: String,
71    #[serde(default)]
72    pub alg: Option<String>,
73    #[serde(default)]
74    pub p2s: Option<String>,
75    #[serde(default)]
76    pub p2c: u32,
77}
78
79impl KeyDerivationInfo {
80    /// The envelope half, without the KDF parameters.
81    pub(super) fn envelope(&self) -> EncryptedEnvelope {
82        EncryptedEnvelope {
83            kid: self.kid.clone(),
84            enc: self.enc.clone(),
85            cty: self.cty.clone(),
86            iv: self.iv.clone(),
87            data: self.data.clone(),
88        }
89    }
90}
91
92/// Response from `v2/auth/methods`.
93#[derive(Debug, Deserialize)]
94pub(super) struct LoginInfo {
95    #[serde(rename = "authMethods")]
96    pub auth_methods: Vec<AuthMethod>,
97}
98
99/// A single auth method offered for an account.
100#[derive(Debug, Deserialize)]
101pub(super) struct AuthMethod {
102    #[serde(rename = "type")]
103    pub kind: String,
104}
105
106/// Response from `v3/auth/start`.
107///
108/// `status` drives the state machine: `ok` carries the SRP parameters, while
109/// `device-not-registered` and `device-deleted` ask the client to (re)authorize the device and
110/// retry.
111#[derive(Debug, Deserialize)]
112pub(super) struct NewSession {
113    pub status: String,
114    #[serde(rename = "sessionID")]
115    pub session_id: String,
116    #[serde(rename = "accountKeyFormat")]
117    pub key_format: Option<String>,
118    #[serde(rename = "accountKeyUuid")]
119    pub key_uuid: Option<String>,
120    #[serde(rename = "userAuth")]
121    pub auth: Option<UserAuth>,
122}
123
124/// The SRP parameters carried by a successful `NewSession`.
125#[derive(Debug, Deserialize)]
126pub(super) struct UserAuth {
127    pub method: String,
128    #[serde(rename = "alg")]
129    pub algorithm: String,
130    pub iterations: u32,
131    pub salt: String,
132}
133
134/// Response from `v1/device` and `v1/device/{uuid}/reauthorize`.
135#[derive(Debug, Deserialize)]
136pub(super) struct SuccessStatus {
137    pub success: i32,
138}
139
140/// Response from `v2/auth` (the SRP A -> B exchange).
141#[derive(Debug, Deserialize)]
142pub(super) struct AForB {
143    #[serde(rename = "userB")]
144    pub b: String,
145}
146
147/// Response from `v2/auth/confirm-key`.
148#[derive(Debug, Deserialize)]
149pub(super) struct ServerHash {
150    #[serde(rename = "serverVerifyHash")]
151    pub server_verify_hash: String,
152}
153
154/// Response from `v2/auth/complete` (decrypted).
155#[derive(Debug, Deserialize)]
156pub(super) struct AuthComplete {
157    pub mfa: Option<MfaInfo>,
158}
159
160/// The set of 2FA methods enabled for an account.
161///
162/// Only the enabled flags are modelled: TOTP is the one interactive method implemented, so the
163/// per-method parameters (WebAuthn challenge, Duo host, and so on) are not read.
164#[derive(Debug, Deserialize)]
165pub(super) struct MfaInfo {
166    #[serde(rename = "totp")]
167    pub google_auth: Option<BasicMfa>,
168    #[serde(rename = "webAuthn")]
169    pub web_authn: Option<BasicMfa>,
170    pub duo: Option<BasicMfa>,
171    #[serde(rename = "dsecret")]
172    pub remember_me: Option<BasicMfa>,
173}
174
175impl MfaInfo {
176    /// Whether TOTP (Google Authenticator) is enabled, the only interactive method supported.
177    pub(super) fn totp_enabled(&self) -> bool {
178        self.google_auth.as_ref().is_some_and(|f| f.enabled)
179    }
180
181    /// Names of the enabled 2FA methods, in the order 1Password reports them.
182    pub(super) fn enabled_methods(&self) -> Vec<&'static str> {
183        let mut methods = Vec::new();
184        for (factor, name) in [
185            (&self.google_auth, "TOTP"),
186            (&self.web_authn, "WebAuthn"),
187            (&self.duo, "Duo"),
188            (&self.remember_me, "remember-me"),
189        ] {
190            if factor.as_ref().is_some_and(|f| f.enabled) {
191                methods.push(name);
192            }
193        }
194        methods
195    }
196}
197
198/// The `{ "enabled": bool }` shared by every 2FA method entry.
199#[derive(Debug, Deserialize)]
200pub(super) struct BasicMfa {
201    pub enabled: bool,
202}
203
204/// A server error body.
205#[derive(Debug, Deserialize)]
206pub(super) struct ErrorResponse {
207    #[serde(rename = "errorCode")]
208    pub code: i32,
209    #[serde(rename = "errorMessage")]
210    pub message: String,
211}
212
213/// A server failure body used by some endpoints instead of `Error`.
214#[derive(Debug, Deserialize)]
215pub(super) struct FailureReason {
216    pub reason: String,
217}
218
219/// Response from `v1/account` (decrypted). Only the vault list is used.
220#[derive(Debug, Deserialize)]
221pub(super) struct AccountInfo {
222    pub vaults: Vec<VaultInfo>,
223}
224
225/// A vault entry in the account info.
226#[derive(Debug, Deserialize)]
227pub(super) struct VaultInfo {
228    pub uuid: String,
229    #[serde(rename = "activeItemCount")]
230    pub active_item_count: Option<u32>,
231    #[serde(rename = "encAttrs")]
232    pub enc_attrs: EncryptedEnvelope,
233    pub access: Vec<VaultAccess>,
234}
235
236/// An access-control entry carrying the vault key encrypted for a key we may hold.
237#[derive(Debug, Deserialize)]
238pub(super) struct VaultAccess {
239    pub acl: i32,
240    #[serde(rename = "encVaultKey")]
241    pub enc_vault_key: EncryptedEnvelope,
242}
243
244/// Decrypted vault attributes.
245#[derive(Deserialize)]
246pub(super) struct VaultAttributes {
247    pub name: Option<String>,
248}
249
250/// A page of vault items. The last page is marked `batchComplete`.
251#[derive(Debug, Deserialize)]
252pub(super) struct VaultItemsBatch {
253    #[serde(rename = "contentVersion")]
254    pub version: i64,
255    #[serde(rename = "batchComplete")]
256    pub complete: bool,
257    pub items: Option<Vec<VaultItem>>,
258}
259
260/// A single encrypted vault item.
261#[derive(Debug, Deserialize)]
262pub(super) struct VaultItem {
263    pub uuid: String,
264    #[serde(rename = "templateUuid")]
265    pub template_uuid: String,
266    pub trashed: String,
267    #[serde(rename = "encOverview")]
268    pub enc_overview: EncryptedEnvelope,
269    #[serde(rename = "encDetails")]
270    pub enc_details: EncryptedEnvelope,
271}
272
273/// A decrypted item overview.
274#[derive(Deserialize)]
275pub struct VaultItemOverview {
276    pub title: Option<String>,
277    pub url: Option<String>,
278    #[serde(rename = "URLs")]
279    pub urls: Option<Vec<VaultItemUrl>>,
280    pub tags: Option<Vec<String>>,
281}
282
283/// A URL entry in an item overview.
284#[derive(Deserialize)]
285pub struct VaultItemUrl {
286    #[serde(rename = "u")]
287    pub url: Option<String>,
288}
289
290/// Decrypted item details.
291#[derive(Deserialize)]
292pub struct VaultItemDetails {
293    #[serde(rename = "notesPlain")]
294    pub note: Option<String>,
295    pub fields: Option<Vec<VaultItemField>>,
296    pub sections: Option<Vec<VaultItemSection>>,
297    /// The secret of a Password-category item, which carries no `fields`.
298    pub password: Option<String>,
299}
300
301/// A designation-based login field (username/password).
302#[derive(Deserialize)]
303pub struct VaultItemField {
304    pub designation: Option<String>,
305    pub value: Option<String>,
306    pub name: Option<String>,
307    /// `T` for text, `P` for password.
308    #[serde(rename = "type")]
309    pub kind: Option<String>,
310}
311
312/// A section of fields. Bitwarden custom fields are a flat list, so only the fields are read.
313#[derive(Deserialize)]
314pub struct VaultItemSection {
315    pub fields: Option<Vec<VaultItemSectionField>>,
316}
317
318/// A field inside a section. The value `v` can be any JSON type.
319#[derive(Deserialize)]
320pub struct VaultItemSectionField {
321    #[serde(rename = "n")]
322    pub id: Option<String>,
323    #[serde(rename = "t")]
324    pub name: Option<String>,
325    #[serde(rename = "v")]
326    pub value: Option<serde_json::Value>,
327    #[serde(rename = "k")]
328    pub kind: Option<String>,
329    #[serde(rename = "a")]
330    pub attributes: Option<VaultItemFieldAttributes>,
331}
332
333/// Extra attributes on a section field.
334#[derive(Deserialize)]
335pub struct VaultItemFieldAttributes {
336    #[serde(rename = "sshKeyAttributes")]
337    pub ssh_key: Option<SshKeyAttributes>,
338}
339
340/// The SSH key material carried on a `sshKey` field.
341#[derive(Deserialize)]
342pub struct SshKeyAttributes {
343    #[serde(rename = "privateKey")]
344    pub private_key: Option<String>,
345    #[serde(rename = "publicKey")]
346    pub public_key: Option<String>,
347    pub fingerprint: Option<String>,
348}