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}
247
248/// A page of vault items. The last page is marked `batchComplete`.
249#[derive(Debug, Deserialize)]
250pub(super) struct VaultItemsBatch {
251    #[serde(rename = "contentVersion")]
252    pub version: i64,
253    #[serde(rename = "batchComplete")]
254    pub complete: bool,
255    pub items: Option<Vec<VaultItem>>,
256}
257
258/// A single encrypted vault item.
259#[derive(Debug, Deserialize)]
260pub(super) struct VaultItem {
261    pub uuid: String,
262    #[serde(rename = "templateUuid")]
263    pub template_uuid: String,
264    pub trashed: String,
265    #[serde(rename = "encOverview")]
266    pub enc_overview: EncryptedEnvelope,
267    #[serde(rename = "encDetails")]
268    pub enc_details: EncryptedEnvelope,
269}
270
271/// A decrypted item overview.
272#[derive(Deserialize)]
273pub struct VaultItemOverview {
274    pub title: Option<String>,
275    pub url: Option<String>,
276    #[serde(rename = "URLs")]
277    pub urls: Option<Vec<VaultItemUrl>>,
278    pub tags: Option<Vec<String>>,
279}
280
281/// A URL entry in an item overview.
282#[derive(Deserialize)]
283pub struct VaultItemUrl {
284    #[serde(rename = "u")]
285    pub url: Option<String>,
286}
287
288/// Decrypted item details.
289#[derive(Deserialize)]
290pub struct VaultItemDetails {
291    #[serde(rename = "notesPlain")]
292    pub note: Option<String>,
293    pub fields: Option<Vec<VaultItemField>>,
294    pub sections: Option<Vec<VaultItemSection>>,
295    /// The secret of a Password-category item, which carries no `fields`.
296    pub password: Option<String>,
297}
298
299/// A designation-based login field (username/password).
300#[derive(Deserialize)]
301pub struct VaultItemField {
302    pub designation: Option<String>,
303    pub value: Option<String>,
304    pub name: Option<String>,
305    /// `T` for text, `P` for password.
306    #[serde(rename = "type")]
307    pub kind: Option<String>,
308}
309
310/// A section of fields. Bitwarden custom fields are a flat list, so only the fields are read.
311#[derive(Deserialize)]
312pub struct VaultItemSection {
313    pub fields: Option<Vec<VaultItemSectionField>>,
314}
315
316/// A field inside a section. The value `v` can be any JSON type.
317#[derive(Deserialize)]
318pub struct VaultItemSectionField {
319    #[serde(rename = "n")]
320    pub id: Option<String>,
321    #[serde(rename = "t")]
322    pub name: Option<String>,
323    #[serde(rename = "v")]
324    pub value: Option<serde_json::Value>,
325    #[serde(rename = "k")]
326    pub kind: Option<String>,
327    #[serde(rename = "a")]
328    pub attributes: Option<VaultItemFieldAttributes>,
329}
330
331/// Extra attributes on a section field.
332#[derive(Deserialize)]
333pub struct VaultItemFieldAttributes {
334    #[serde(rename = "sshKeyAttributes")]
335    pub ssh_key: Option<SshKeyAttributes>,
336}
337
338/// The SSH key material carried on a `sshKey` field.
339#[derive(Deserialize)]
340pub struct SshKeyAttributes {
341    #[serde(rename = "privateKey")]
342    pub private_key: Option<String>,
343    #[serde(rename = "publicKey")]
344    pub public_key: Option<String>,
345    pub fingerprint: Option<String>,
346}