deltachat/
token.rs

1//! # Token module.
2//!
3//! Functions to read/write token from/to the database. A token is any string associated with a key.
4//!
5//! Tokens are used in SecureJoin verification protocols.
6
7use anyhow::Result;
8use deltachat_derive::{FromSql, ToSql};
9
10use crate::context::Context;
11use crate::tools::{create_id, time};
12
13/// Token namespace
14#[derive(
15    Debug, Default, Display, Clone, Copy, PartialEq, Eq, FromPrimitive, ToPrimitive, ToSql, FromSql,
16)]
17#[repr(u32)]
18pub enum Namespace {
19    #[default]
20    Unknown = 0,
21    Auth = 110,
22    InviteNumber = 100,
23}
24
25/// Saves a token to the database.
26pub async fn save(
27    context: &Context,
28    namespace: Namespace,
29    foreign_key: Option<&str>,
30    token: &str,
31    timestamp: i64,
32) -> Result<()> {
33    if token.is_empty() {
34        info!(context, "Not saving empty {namespace} token");
35        return Ok(());
36    }
37    context
38        .sql
39        .execute(
40            "INSERT OR IGNORE INTO tokens (namespc, foreign_key, token, timestamp) VALUES (?, ?, ?, ?)",
41            (namespace, foreign_key.unwrap_or(""), token, timestamp),
42        )
43        .await?;
44    Ok(())
45}
46
47/// Looks up most recently created token for a namespace / foreign key combination.
48///
49/// As there may be more than one such valid token,
50/// (eg. when a qr code token is withdrawn, recreated and revived later),
51/// use lookup() for qr-code creation only;
52/// do not use lookup() to check for token validity.
53///
54/// To check if a given token is valid, use exists().
55pub async fn lookup(
56    context: &Context,
57    namespace: Namespace,
58    foreign_key: Option<&str>,
59) -> Result<Option<String>> {
60    context
61        .sql
62        .query_get_value(
63            "SELECT token FROM tokens WHERE namespc=? AND foreign_key=? ORDER BY id DESC LIMIT 1",
64            (namespace, foreign_key.unwrap_or("")),
65        )
66        .await
67}
68
69pub async fn lookup_or_new(
70    context: &Context,
71    namespace: Namespace,
72    foreign_key: Option<&str>,
73) -> Result<String> {
74    if let Some(token) = lookup(context, namespace, foreign_key).await? {
75        return Ok(token);
76    }
77
78    let token = create_id();
79    let timestamp = time();
80    save(context, namespace, foreign_key, &token, timestamp).await?;
81    Ok(token)
82}
83
84pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> Result<bool> {
85    let exists = context
86        .sql
87        .exists(
88            "SELECT COUNT(*) FROM tokens WHERE namespc=? AND token=?;",
89            (namespace, token),
90        )
91        .await?;
92    Ok(exists)
93}
94
95/// Resets all tokens corresponding to the `foreign_key`.
96///
97/// `foreign_key` is a group ID to reset all group tokens
98/// or empty string to reset all setup contact tokens.
99pub async fn delete(context: &Context, foreign_key: &str) -> Result<()> {
100    context
101        .sql
102        .execute("DELETE FROM tokens WHERE foreign_key=?", (foreign_key,))
103        .await?;
104    Ok(())
105}