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) -> Result<()> {
32    context
33        .sql
34        .execute(
35            "INSERT INTO tokens (namespc, foreign_key, token, timestamp) VALUES (?, ?, ?, ?)",
36            (namespace, foreign_key.unwrap_or(""), token, time()),
37        )
38        .await?;
39    Ok(())
40}
41
42/// Looks up most recently created token for a namespace / foreign key combination.
43///
44/// As there may be more than one such valid token,
45/// (eg. when a qr code token is withdrawn, recreated and revived later),
46/// use lookup() for qr-code creation only;
47/// do not use lookup() to check for token validity.
48///
49/// To check if a given token is valid, use exists().
50pub async fn lookup(
51    context: &Context,
52    namespace: Namespace,
53    foreign_key: Option<&str>,
54) -> Result<Option<String>> {
55    context
56        .sql
57        .query_get_value(
58            "SELECT token FROM tokens WHERE namespc=? AND foreign_key=? ORDER BY timestamp DESC LIMIT 1",
59            (namespace, foreign_key.unwrap_or("")),
60        )
61        .await
62}
63
64pub async fn lookup_or_new(
65    context: &Context,
66    namespace: Namespace,
67    foreign_key: Option<&str>,
68) -> Result<String> {
69    if let Some(token) = lookup(context, namespace, foreign_key).await? {
70        return Ok(token);
71    }
72
73    let token = create_id();
74    save(context, namespace, foreign_key, &token).await?;
75    Ok(token)
76}
77
78pub async fn exists(context: &Context, namespace: Namespace, token: &str) -> Result<bool> {
79    let exists = context
80        .sql
81        .exists(
82            "SELECT COUNT(*) FROM tokens WHERE namespc=? AND token=?;",
83            (namespace, token),
84        )
85        .await?;
86    Ok(exists)
87}
88
89/// Looks up foreign key by auth token.
90///
91/// Returns None if auth token is not valid.
92/// Returns an empty string if the token corresponds to "setup contact" rather than group join.
93pub async fn auth_foreign_key(context: &Context, token: &str) -> Result<Option<String>> {
94    context
95        .sql
96        .query_row_optional(
97            "SELECT foreign_key FROM tokens WHERE namespc=? AND token=?",
98            (Namespace::Auth, token),
99            |row| {
100                let foreign_key: String = row.get(0)?;
101                Ok(foreign_key)
102            },
103        )
104        .await
105}
106
107pub async fn delete(context: &Context, namespace: Namespace, token: &str) -> Result<()> {
108    context
109        .sql
110        .execute(
111            "DELETE FROM tokens WHERE namespc=? AND token=?;",
112            (namespace, token),
113        )
114        .await?;
115    Ok(())
116}