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 = "encAttrs")]
230    pub enc_attrs: EncryptedEnvelope,
231    pub access: Vec<VaultAccess>,
232}
233
234/// An access-control entry carrying the vault key encrypted for a key we may hold.
235#[derive(Debug, Deserialize)]
236pub(super) struct VaultAccess {
237    pub acl: i32,
238    #[serde(rename = "encVaultKey")]
239    pub enc_vault_key: EncryptedEnvelope,
240}
241
242/// Decrypted vault attributes.
243#[derive(Deserialize)]
244pub(super) struct VaultAttributes {
245    pub name: Option<String>,
246    pub desc: Option<String>,
247}
248
249/// A page of vault items. The last page is marked `batchComplete`.
250#[derive(Debug, Deserialize)]
251pub(super) struct VaultItemsBatch {
252    #[serde(rename = "contentVersion")]
253    pub version: i64,
254    #[serde(rename = "batchComplete")]
255    pub complete: bool,
256    pub items: Option<Vec<VaultItem>>,
257}
258
259/// A single encrypted vault item.
260#[derive(Debug, Deserialize)]
261pub(super) struct VaultItem {
262    pub uuid: String,
263    #[serde(rename = "templateUuid")]
264    pub template_uuid: String,
265    pub trashed: String,
266    #[serde(rename = "encOverview")]
267    pub enc_overview: EncryptedEnvelope,
268    #[serde(rename = "encDetails")]
269    pub enc_details: EncryptedEnvelope,
270}
271
272/// A decrypted item overview.
273#[derive(Deserialize)]
274pub struct VaultItemOverview {
275    pub title: Option<String>,
276    pub ainfo: 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 = "l")]
287    pub name: Option<String>,
288    #[serde(rename = "u")]
289    pub url: Option<String>,
290}
291
292/// Decrypted item details.
293#[derive(Deserialize)]
294pub struct VaultItemDetails {
295    #[serde(rename = "notesPlain")]
296    pub note: Option<String>,
297    pub fields: Option<Vec<VaultItemField>>,
298    pub sections: Option<Vec<VaultItemSection>>,
299    /// The secret of a Password-category item, which carries no `fields`.
300    pub password: Option<String>,
301    #[serde(rename = "passwordHistory")]
302    pub password_history: Option<Vec<VaultItemPasswordHistory>>,
303}
304
305/// A superseded password and the unix time it was replaced, oldest first.
306#[derive(Deserialize)]
307pub struct VaultItemPasswordHistory {
308    pub value: Option<String>,
309    pub time: Option<i64>,
310}
311
312/// A designation-based login field (username/password).
313#[derive(Deserialize)]
314pub struct VaultItemField {
315    pub designation: Option<String>,
316    pub value: Option<String>,
317    pub name: Option<String>,
318    /// `T` for text, `P` for password.
319    #[serde(rename = "type")]
320    pub kind: Option<String>,
321}
322
323/// A titled section of fields.
324#[derive(Deserialize)]
325pub struct VaultItemSection {
326    /// The section's stable id, such as `Section_l2bagl3iupehvr7jvrc62mjhee`.
327    #[serde(rename = "name")]
328    pub id: Option<String>,
329    #[serde(rename = "title")]
330    pub name: Option<String>,
331    pub fields: Option<Vec<VaultItemSectionField>>,
332}
333
334/// A field inside a section. The value `v` can be any JSON type.
335#[derive(Deserialize)]
336pub struct VaultItemSectionField {
337    #[serde(rename = "n")]
338    pub id: Option<String>,
339    #[serde(rename = "t")]
340    pub name: Option<String>,
341    #[serde(rename = "v")]
342    pub value: Option<serde_json::Value>,
343    #[serde(rename = "k")]
344    pub kind: Option<String>,
345    #[serde(rename = "a")]
346    pub attributes: Option<VaultItemFieldAttributes>,
347    /// Keyboard hints for the 1Password UI, of no use to an import.
348    #[serde(rename = "inputTraits")]
349    pub input_traits: Option<VaultItemInputTraits>,
350}
351
352/// How the 1Password UI should present a field's editor.
353#[derive(Debug, Deserialize)]
354pub struct VaultItemInputTraits {
355    pub autocapitalization: Option<String>,
356    pub keyboard: Option<String>,
357    pub correction: Option<String>,
358}
359
360/// Extra attributes on a section field.
361#[derive(Deserialize)]
362pub struct VaultItemFieldAttributes {
363    pub guarded: Option<String>,
364    #[serde(rename = "sshKeyAttributes")]
365    pub ssh_key: Option<SshKeyAttributes>,
366}
367
368/// The SSH key material carried on a `sshKey` field.
369#[derive(Deserialize)]
370pub struct SshKeyAttributes {
371    #[serde(rename = "privateKey")]
372    pub private_key: Option<String>,
373    #[serde(rename = "publicKey")]
374    pub public_key: Option<String>,
375    pub fingerprint: Option<String>,
376    #[serde(rename = "keyType")]
377    pub key_type: Option<SshKeyType>,
378}
379
380/// An SSH key's type and, for RSA, its bit length.
381#[derive(Debug, Deserialize)]
382pub struct SshKeyType {
383    #[serde(rename = "t")]
384    pub kind: String,
385    #[serde(rename = "c", default)]
386    pub bits: i64,
387}