Skip to main content

deltachat/securejoin/
qrinvite.rs

1//! Supporting code for the QR-code invite.
2//!
3//! QR-codes are decoded into a more general-purpose [`Qr`] struct normally.  This makes working
4//! with it rather hard, so here we have a wrapper type that specifically deals with Secure-Join
5//! QR-codes so that the Secure-Join code can have more guarantees when dealing with this.
6
7use anyhow::{Error, Result, bail};
8
9use crate::contact::ContactId;
10use crate::key::Fingerprint;
11use crate::qr::Qr;
12
13/// Represents the data from a QR-code scan.
14///
15/// There are methods to conveniently access fields present in all three variants.
16#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
17pub enum QrInvite {
18    Contact {
19        contact_id: ContactId,
20        fingerprint: Fingerprint,
21        #[serde(default)]
22        addrs: Vec<String>,
23        invitenumber: String,
24        authcode: String,
25        #[serde(default)]
26        is_v3: bool,
27    },
28    Group {
29        contact_id: ContactId,
30        fingerprint: Fingerprint,
31        #[serde(default)]
32        addrs: Vec<String>,
33        name: String,
34        grpid: String,
35        invitenumber: String,
36        authcode: String,
37        #[serde(default)]
38        is_v3: bool,
39    },
40    Broadcast {
41        contact_id: ContactId,
42        fingerprint: Fingerprint,
43        #[serde(default)]
44        addrs: Vec<String>,
45        name: String,
46        grpid: String,
47        invitenumber: String,
48        authcode: String,
49        #[serde(default)]
50        is_v3: bool,
51    },
52}
53
54impl QrInvite {
55    /// The contact ID of the inviter.
56    ///
57    /// The actual QR-code contains a URL-encoded email address, but upon scanning this is
58    /// translated to a contact ID.
59    pub fn contact_id(&self) -> ContactId {
60        match self {
61            Self::Contact { contact_id, .. }
62            | Self::Group { contact_id, .. }
63            | Self::Broadcast { contact_id, .. } => *contact_id,
64        }
65    }
66
67    /// The fingerprint of the inviter.
68    pub fn fingerprint(&self) -> &Fingerprint {
69        match self {
70            Self::Contact { fingerprint, .. }
71            | Self::Group { fingerprint, .. }
72            | Self::Broadcast { fingerprint, .. } => fingerprint,
73        }
74    }
75
76    /// The `INVITENUMBER` of the setup-contact/secure-join protocol.
77    pub fn invitenumber(&self) -> &str {
78        match self {
79            Self::Contact { invitenumber, .. }
80            | Self::Group { invitenumber, .. }
81            | Self::Broadcast { invitenumber, .. } => invitenumber,
82        }
83    }
84
85    /// The `AUTH` code of the setup-contact/secure-join protocol.
86    pub fn authcode(&self) -> &str {
87        match self {
88            Self::Contact { authcode, .. }
89            | Self::Group { authcode, .. }
90            | Self::Broadcast { authcode, .. } => authcode,
91        }
92    }
93
94    pub fn is_v3(&self) -> bool {
95        match *self {
96            QrInvite::Contact { is_v3, .. } => is_v3,
97            QrInvite::Group { is_v3, .. } => is_v3,
98            QrInvite::Broadcast { is_v3, .. } => is_v3,
99        }
100    }
101
102    pub(crate) fn addrs(&self) -> &Vec<String> {
103        match self {
104            QrInvite::Contact { addrs, .. } => addrs,
105            QrInvite::Group { addrs, .. } => addrs,
106            QrInvite::Broadcast { addrs, .. } => addrs,
107        }
108    }
109}
110
111impl TryFrom<Qr> for QrInvite {
112    type Error = Error;
113
114    fn try_from(qr: Qr) -> Result<Self> {
115        match qr {
116            Qr::AskVerifyContact {
117                contact_id,
118                fingerprint,
119                addrs,
120                invitenumber,
121                authcode,
122                is_v3,
123            } => Ok(QrInvite::Contact {
124                contact_id,
125                fingerprint,
126                addrs,
127                invitenumber,
128                authcode,
129                is_v3,
130            }),
131            Qr::AskVerifyGroup {
132                grpname,
133                grpid,
134                contact_id,
135                fingerprint,
136                addrs,
137                invitenumber,
138                authcode,
139                is_v3,
140            } => Ok(QrInvite::Group {
141                contact_id,
142                fingerprint,
143                addrs,
144                name: grpname,
145                grpid,
146                invitenumber,
147                authcode,
148                is_v3,
149            }),
150            Qr::AskJoinBroadcast {
151                name,
152                grpid,
153                contact_id,
154                fingerprint,
155                addrs,
156                authcode,
157                invitenumber,
158                is_v3,
159            } => Ok(QrInvite::Broadcast {
160                name,
161                grpid,
162                contact_id,
163                fingerprint,
164                addrs,
165                authcode,
166                invitenumber,
167                is_v3,
168            }),
169            _ => bail!("Unsupported QR type"),
170        }
171    }
172}
173
174impl rusqlite::types::ToSql for QrInvite {
175    fn to_sql(&self) -> rusqlite::Result<rusqlite::types::ToSqlOutput<'_>> {
176        let json = serde_json::to_string(self)
177            .map_err(|err| rusqlite::Error::ToSqlConversionFailure(Box::new(err)))?;
178        let val = rusqlite::types::Value::Text(json);
179        let out = rusqlite::types::ToSqlOutput::Owned(val);
180        Ok(out)
181    }
182}
183
184impl rusqlite::types::FromSql for QrInvite {
185    fn column_result(value: rusqlite::types::ValueRef<'_>) -> rusqlite::types::FromSqlResult<Self> {
186        String::column_result(value).and_then(|val| {
187            serde_json::from_str(&val)
188                .map_err(|err| rusqlite::types::FromSqlError::Other(Box::new(err)))
189        })
190    }
191}