mirror of
https://github.com/chatmail/core.git
synced 2026-05-20 23:36:30 +03:00
feat: Decrypt and answer on Alice's side (sending the message seems not to work yet)
This commit is contained in:
@@ -2064,6 +2064,22 @@ pub(crate) async fn set_msg_failed(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts a tombstone into `msgs` table
|
||||||
|
/// to prevent downloading the same message in the future.
|
||||||
|
///
|
||||||
|
/// Returns tombstone database row ID.
|
||||||
|
pub(crate) async fn insert_tombstone(context: &Context, rfc724_mid: &str) -> Result<MsgId> {
|
||||||
|
let row_id = context
|
||||||
|
.sql
|
||||||
|
.insert(
|
||||||
|
"INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)",
|
||||||
|
(rfc724_mid, DC_CHAT_ID_TRASH),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
let msg_id = MsgId::new(u32::try_from(row_id)?);
|
||||||
|
Ok(msg_id)
|
||||||
|
}
|
||||||
|
|
||||||
/// The number of messages assigned to unblocked chats
|
/// The number of messages assigned to unblocked chats
|
||||||
pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
|
pub async fn get_unblocked_msg_cnt(context: &Context) -> usize {
|
||||||
match context
|
match context
|
||||||
|
|||||||
@@ -2367,7 +2367,7 @@ pub(crate) async fn render_symm_encrypted_securejoin_message(
|
|||||||
// leaking information about the tokens.
|
// leaking information about the tokens.
|
||||||
let compress = false;
|
let compress = false;
|
||||||
let encrypted = encrypt_helper
|
let encrypted = encrypt_helper
|
||||||
.encrypt_symmetrically(context, auth, message, compress)
|
.encrypt_symmetrically(context, auth, message, compress) // TODO this also signs the message. vc-request-pubkey shouldn't be signed.
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
wrap_encrypted_part(encrypted)
|
wrap_encrypted_part(encrypted)
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ use crate::authres::handle_authres;
|
|||||||
use crate::blob::BlobObject;
|
use crate::blob::BlobObject;
|
||||||
use crate::chat::ChatId;
|
use crate::chat::ChatId;
|
||||||
use crate::config::Config;
|
use crate::config::Config;
|
||||||
use crate::constants;
|
|
||||||
use crate::contact::ContactId;
|
use crate::contact::ContactId;
|
||||||
use crate::context::Context;
|
use crate::context::Context;
|
||||||
use crate::decrypt::{try_decrypt, validate_detached_signature};
|
use crate::decrypt::{try_decrypt, validate_detached_signature};
|
||||||
@@ -36,6 +35,7 @@ use crate::tools::{
|
|||||||
get_filemeta, parse_receive_headers, smeared_time, time, truncate_msg_text, validate_id,
|
get_filemeta, parse_receive_headers, smeared_time, time, truncate_msg_text, validate_id,
|
||||||
};
|
};
|
||||||
use crate::{chatlist_events, location, tools};
|
use crate::{chatlist_events, location, tools};
|
||||||
|
use crate::{constants, token};
|
||||||
|
|
||||||
/// Public key extracted from `Autocrypt-Gossip`
|
/// Public key extracted from `Autocrypt-Gossip`
|
||||||
/// header with associated information.
|
/// header with associated information.
|
||||||
@@ -382,7 +382,7 @@ impl MimeMessage {
|
|||||||
|
|
||||||
let mail_raw; // Memory location for a possible decrypted message.
|
let mail_raw; // Memory location for a possible decrypted message.
|
||||||
let decrypted_msg; // Decrypted signed OpenPGP message.
|
let decrypted_msg; // Decrypted signed OpenPGP message.
|
||||||
let secrets: Vec<String> = context
|
let mut secrets: Vec<String> = context
|
||||||
.sql
|
.sql
|
||||||
.query_map_vec("SELECT secret FROM broadcast_secrets", (), |row| {
|
.query_map_vec("SELECT secret FROM broadcast_secrets", (), |row| {
|
||||||
let secret: String = row.get(0)?;
|
let secret: String = row.get(0)?;
|
||||||
@@ -390,6 +390,8 @@ impl MimeMessage {
|
|||||||
})
|
})
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
|
secrets.extend(token::lookup_all(context, token::Namespace::Auth).await?);
|
||||||
|
|
||||||
let (mail, is_encrypted) =
|
let (mail, is_encrypted) =
|
||||||
match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring, &secrets)) {
|
match tokio::task::block_in_place(|| try_decrypt(&mail, &private_keyring, &secrets)) {
|
||||||
Ok(Some(mut msg)) => {
|
Ok(Some(mut msg)) => {
|
||||||
|
|||||||
@@ -177,22 +177,6 @@ pub(crate) async fn receive_imf_from_inbox(
|
|||||||
receive_imf_inner(context, rfc724_mid, imf_raw, seen).await
|
receive_imf_inner(context, rfc724_mid, imf_raw, seen).await
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a tombstone into `msgs` table
|
|
||||||
/// to prevent downloading the same message in the future.
|
|
||||||
///
|
|
||||||
/// Returns tombstone database row ID.
|
|
||||||
async fn insert_tombstone(context: &Context, rfc724_mid: &str) -> Result<MsgId> {
|
|
||||||
let row_id = context
|
|
||||||
.sql
|
|
||||||
.insert(
|
|
||||||
"INSERT INTO msgs(rfc724_mid, chat_id) VALUES (?,?)",
|
|
||||||
(rfc724_mid, DC_CHAT_ID_TRASH),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let msg_id = MsgId::new(u32::try_from(row_id)?);
|
|
||||||
Ok(msg_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_to_and_past_contact_ids(
|
async fn get_to_and_past_contact_ids(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
mime_parser: &MimeMessage,
|
mime_parser: &MimeMessage,
|
||||||
@@ -477,7 +461,7 @@ pub(crate) async fn receive_imf_inner(
|
|||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let msg_ids = vec![insert_tombstone(context, rfc724_mid).await?];
|
let msg_ids = vec![message::insert_tombstone(context, rfc724_mid).await?];
|
||||||
|
|
||||||
return Ok(Some(ReceivedMsg {
|
return Ok(Some(ReceivedMsg {
|
||||||
chat_id: DC_CHAT_ID_TRASH,
|
chat_id: DC_CHAT_ID_TRASH,
|
||||||
@@ -653,7 +637,7 @@ pub(crate) async fn receive_imf_inner(
|
|||||||
|
|
||||||
match res {
|
match res {
|
||||||
securejoin::HandshakeMessage::Done | securejoin::HandshakeMessage::Ignore => {
|
securejoin::HandshakeMessage::Done | securejoin::HandshakeMessage::Ignore => {
|
||||||
let msg_id = insert_tombstone(context, rfc724_mid).await?;
|
let msg_id = message::insert_tombstone(context, rfc724_mid).await?;
|
||||||
received_msg = Some(ReceivedMsg {
|
received_msg = Some(ReceivedMsg {
|
||||||
chat_id: DC_CHAT_ID_TRASH,
|
chat_id: DC_CHAT_ID_TRASH,
|
||||||
state: MessageState::InSeen,
|
state: MessageState::InSeen,
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ use crate::headerdef::HeaderDef;
|
|||||||
use crate::key::{DcKey, Fingerprint, load_self_public_key};
|
use crate::key::{DcKey, Fingerprint, load_self_public_key};
|
||||||
use crate::log::LogExt as _;
|
use crate::log::LogExt as _;
|
||||||
use crate::log::warn;
|
use crate::log::warn;
|
||||||
use crate::message::{Message, Viewtype};
|
use crate::message::{self, Message, MsgId, Viewtype};
|
||||||
use crate::mimeparser::{MimeMessage, SystemMessage};
|
use crate::mimeparser::{MimeMessage, SystemMessage};
|
||||||
use crate::param::Param;
|
use crate::param::Param;
|
||||||
use crate::qr::check_qr;
|
use crate::qr::check_qr;
|
||||||
use crate::securejoin::bob::JoinerProgress;
|
use crate::securejoin::bob::JoinerProgress;
|
||||||
use crate::sync::Sync::*;
|
use crate::sync::Sync::*;
|
||||||
use crate::tools::{create_id, time};
|
use crate::tools::{create_id, create_outgoing_rfc724_mid, time};
|
||||||
use crate::{SecurejoinSource, stats};
|
use crate::{SecurejoinSource, mimefactory, stats};
|
||||||
use crate::{SecurejoinUiPath, token};
|
use crate::{SecurejoinUiPath, token};
|
||||||
|
|
||||||
mod bob;
|
mod bob;
|
||||||
@@ -346,12 +346,18 @@ pub(crate) enum HandshakeMessage {
|
|||||||
/// Step of Secure-Join protocol.
|
/// Step of Secure-Join protocol.
|
||||||
#[derive(Debug, Display, PartialEq, Eq)]
|
#[derive(Debug, Display, PartialEq, Eq)]
|
||||||
pub(crate) enum SecureJoinStep {
|
pub(crate) enum SecureJoinStep {
|
||||||
/// vc-request or vg-request
|
/// vc-request or vg-request; only used in legacy securejoin
|
||||||
Request { invitenumber: String },
|
Request { invitenumber: String },
|
||||||
|
|
||||||
/// vc-auth-required or vg-auth-required
|
/// vc-auth-required or vg-auth-required; only used in legacy securejoin
|
||||||
AuthRequired,
|
AuthRequired,
|
||||||
|
|
||||||
|
/// vc-request-pubkey; only used in securejoin v3
|
||||||
|
RequestPubkey,
|
||||||
|
|
||||||
|
/// vc-pubkey; only used in securejoin v3
|
||||||
|
Pubkey,
|
||||||
|
|
||||||
/// vc-request-with-auth or vg-request-with-auth
|
/// vc-request-with-auth or vg-request-with-auth
|
||||||
RequestWithAuth,
|
RequestWithAuth,
|
||||||
|
|
||||||
@@ -381,6 +387,8 @@ pub(crate) fn get_secure_join_step(mime_message: &MimeMessage) -> Option<SecureJ
|
|||||||
})
|
})
|
||||||
} else if let Some(step) = mime_message.get_header(HeaderDef::SecureJoin) {
|
} else if let Some(step) = mime_message.get_header(HeaderDef::SecureJoin) {
|
||||||
match step {
|
match step {
|
||||||
|
"vc-request-pubkey" => Some(SecureJoinStep::RequestPubkey),
|
||||||
|
"vc-pubkey" => Some(SecureJoinStep::Pubkey),
|
||||||
"vg-auth-required" | "vc-auth-required" => Some(SecureJoinStep::AuthRequired),
|
"vg-auth-required" | "vc-auth-required" => Some(SecureJoinStep::AuthRequired),
|
||||||
"vg-request-with-auth" | "vc-request-with-auth" => {
|
"vg-request-with-auth" | "vc-request-with-auth" => {
|
||||||
Some(SecureJoinStep::RequestWithAuth)
|
Some(SecureJoinStep::RequestWithAuth)
|
||||||
@@ -438,7 +446,10 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
// will improve security (completely unrelated to the securejoin protocol)
|
// will improve security (completely unrelated to the securejoin protocol)
|
||||||
// and is something we want to do in the future:
|
// and is something we want to do in the future:
|
||||||
// https://www.rfc-editor.org/rfc/rfc9580.html#name-surreptitious-forwarding
|
// https://www.rfc-editor.org/rfc/rfc9580.html#name-surreptitious-forwarding
|
||||||
if !matches!(step, SecureJoinStep::Request { .. }) {
|
if !matches!(
|
||||||
|
step,
|
||||||
|
SecureJoinStep::Request { .. } | SecureJoinStep::RequestPubkey
|
||||||
|
) {
|
||||||
let mut self_found = false;
|
let mut self_found = false;
|
||||||
let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint();
|
let self_fingerprint = load_self_public_key(context).await?.dc_fingerprint();
|
||||||
for (addr, key) in &mime_message.gossiped_keys {
|
for (addr, key) in &mime_message.gossiped_keys {
|
||||||
@@ -506,6 +517,53 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
========================================================*/
|
========================================================*/
|
||||||
bob::handle_auth_required(context, mime_message).await
|
bob::handle_auth_required(context, mime_message).await
|
||||||
}
|
}
|
||||||
|
SecureJoinStep::RequestPubkey => {
|
||||||
|
/*========================================================
|
||||||
|
==== Alice - the inviter's side =====
|
||||||
|
==== Bob requests our public key (Securejoin v3) =====
|
||||||
|
========================================================*/
|
||||||
|
|
||||||
|
if !mime_message.was_encrypted() {
|
||||||
|
warn!(context, "Ignoring unencrypted RequestPubkey");
|
||||||
|
return Ok(HandshakeMessage::Ignore);
|
||||||
|
}
|
||||||
|
let Some(auth) = mime_message.get_header(HeaderDef::SecureJoinAuth) else {
|
||||||
|
warn!(
|
||||||
|
context,
|
||||||
|
"Ignoring {step} message because of missing auth code."
|
||||||
|
);
|
||||||
|
return Ok(HandshakeMessage::Ignore);
|
||||||
|
};
|
||||||
|
if !token::exists(context, token::Namespace::Auth, auth).await? {
|
||||||
|
warn!(context, "Secure-join denied (bad auth).");
|
||||||
|
return Ok(HandshakeMessage::Ignore);
|
||||||
|
}
|
||||||
|
|
||||||
|
let rfc724_mid = create_outgoing_rfc724_mid();
|
||||||
|
let addr = ContactAddress::new(&mime_message.from.addr)?;
|
||||||
|
let attach_self_pubkey = true;
|
||||||
|
let rendered_message = mimefactory::render_symm_encrypted_securejoin_message(
|
||||||
|
context,
|
||||||
|
"vc-pubkey",
|
||||||
|
&rfc724_mid,
|
||||||
|
attach_self_pubkey,
|
||||||
|
auth,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let msg_id = message::insert_tombstone(context, &rfc724_mid).await?;
|
||||||
|
insert_into_smtp(context, &rfc724_mid, &addr, rendered_message, msg_id).await?;
|
||||||
|
context.scheduler.interrupt_smtp().await;
|
||||||
|
|
||||||
|
Ok(HandshakeMessage::Done)
|
||||||
|
}
|
||||||
|
SecureJoinStep::Pubkey => {
|
||||||
|
/*========================================================
|
||||||
|
==== Bob - the joiner's side =====
|
||||||
|
==== Alice sent us her pubkey (Securejoin v3) =====
|
||||||
|
========================================================*/
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
SecureJoinStep::RequestWithAuth => {
|
SecureJoinStep::RequestWithAuth => {
|
||||||
/*==========================================================
|
/*==========================================================
|
||||||
==== Alice - the inviter side ====
|
==== Alice - the inviter side ====
|
||||||
@@ -665,6 +723,24 @@ pub(crate) async fn handle_securejoin_handshake(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn insert_into_smtp(
|
||||||
|
context: &Context,
|
||||||
|
rfc724_mid: &str,
|
||||||
|
recipient: &str,
|
||||||
|
rendered_message: String,
|
||||||
|
msg_id: MsgId,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.execute(
|
||||||
|
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
|
||||||
|
VALUES (?1, ?2, ?3, ?4)",
|
||||||
|
(&rfc724_mid, &recipient, &rendered_message, msg_id),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Observe self-sent Securejoin message.
|
/// Observe self-sent Securejoin message.
|
||||||
///
|
///
|
||||||
/// In a multi-device-setup, there may be other devices that "see" the handshake messages.
|
/// In a multi-device-setup, there may be other devices that "see" the handshake messages.
|
||||||
@@ -696,6 +772,8 @@ pub(crate) async fn observe_securejoin_on_other_device(
|
|||||||
match step {
|
match step {
|
||||||
SecureJoinStep::Request { .. }
|
SecureJoinStep::Request { .. }
|
||||||
| SecureJoinStep::AuthRequired
|
| SecureJoinStep::AuthRequired
|
||||||
|
| SecureJoinStep::RequestPubkey
|
||||||
|
| SecureJoinStep::Pubkey
|
||||||
| SecureJoinStep::Deprecated
|
| SecureJoinStep::Deprecated
|
||||||
| SecureJoinStep::Unknown { .. } => {
|
| SecureJoinStep::Unknown { .. } => {
|
||||||
return Ok(HandshakeMessage::Ignore);
|
return Ok(HandshakeMessage::Ignore);
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ use crate::context::Context;
|
|||||||
use crate::events::EventType;
|
use crate::events::EventType;
|
||||||
use crate::key::self_fingerprint;
|
use crate::key::self_fingerprint;
|
||||||
use crate::log::LogExt;
|
use crate::log::LogExt;
|
||||||
use crate::message::{Message, MsgId, Viewtype};
|
use crate::message::{self, Message, MsgId, Viewtype};
|
||||||
use crate::mimeparser::{MimeMessage, SystemMessage};
|
use crate::mimeparser::{MimeMessage, SystemMessage};
|
||||||
use crate::param::{Param, Params};
|
use crate::param::{Param, Params};
|
||||||
use crate::securejoin::{ContactId, encrypted_and_signed, verify_sender_by_fingerprint};
|
use crate::securejoin::{
|
||||||
|
ContactId, encrypted_and_signed, insert_into_smtp, verify_sender_by_fingerprint,
|
||||||
|
};
|
||||||
use crate::stock_str;
|
use crate::stock_str;
|
||||||
use crate::sync::Sync::*;
|
use crate::sync::Sync::*;
|
||||||
use crate::tools::{create_outgoing_rfc724_mid, smeared_time, time};
|
use crate::tools::{create_outgoing_rfc724_mid, smeared_time, time};
|
||||||
@@ -302,95 +304,21 @@ pub(crate) async fn send_handshake_message(
|
|||||||
if invite.is_v3() && matches!(step, BobHandshakeMsg::Request) {
|
if invite.is_v3() && matches!(step, BobHandshakeMsg::Request) {
|
||||||
// Send a minimal symmetrically-encrypted vc-request message
|
// Send a minimal symmetrically-encrypted vc-request message
|
||||||
|
|
||||||
// TODO: Either add a message to the database, or make sure that smtp.rs gets along with a 0 or NULL msg_id
|
|
||||||
/*
|
|
||||||
msg.state = MessageState::OutPending;
|
|
||||||
msg.timestamp_sort = create_smeared_timestamp(context);
|
|
||||||
msg.rfc724_mid = create_outgoing_rfc724_mid();
|
|
||||||
let is_bot = context.get_config_bool(Config::Bot).await?;
|
|
||||||
msg.param
|
|
||||||
.set_optional(Param::Bot, Some("1").filter(|_| is_bot));
|
|
||||||
|
|
||||||
let raw_id = context
|
|
||||||
.sql
|
|
||||||
.insert(
|
|
||||||
"INSERT INTO msgs (
|
|
||||||
rfc724_mid,
|
|
||||||
chat_id,
|
|
||||||
from_id,
|
|
||||||
to_id,
|
|
||||||
timestamp,
|
|
||||||
type,
|
|
||||||
state,
|
|
||||||
txt,
|
|
||||||
txt_normalized,
|
|
||||||
subject,
|
|
||||||
param,
|
|
||||||
hidden,
|
|
||||||
mime_in_reply_to,
|
|
||||||
mime_references,
|
|
||||||
mime_modified,
|
|
||||||
mime_headers,
|
|
||||||
mime_compressed,
|
|
||||||
location_id,
|
|
||||||
ephemeral_timer,
|
|
||||||
ephemeral_timestamp)
|
|
||||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1,?,?,?);",
|
|
||||||
params_slice![
|
|
||||||
msg.rfc724_mid,
|
|
||||||
msg.chat_id,
|
|
||||||
msg.from_id,
|
|
||||||
to_id,
|
|
||||||
msg.timestamp_sort,
|
|
||||||
msg.viewtype,
|
|
||||||
msg.state,
|
|
||||||
msg_text,
|
|
||||||
normalize_text(&msg_text),
|
|
||||||
&msg.subject,
|
|
||||||
msg.param.to_string(),
|
|
||||||
msg.hidden,
|
|
||||||
msg.in_reply_to.as_deref().unwrap_or_default(),
|
|
||||||
new_references,
|
|
||||||
new_mime_headers.is_some(),
|
|
||||||
new_mime_headers.unwrap_or_default(),
|
|
||||||
location_id as i32,
|
|
||||||
ephemeral_timer,
|
|
||||||
ephemeral_timestamp
|
|
||||||
],
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
context.new_msgs_notify.notify_one();
|
|
||||||
msg.id = MsgId::new(u32::try_from(raw_id)?);
|
|
||||||
*/
|
|
||||||
|
|
||||||
let rfc724_mid = create_outgoing_rfc724_mid();
|
let rfc724_mid = create_outgoing_rfc724_mid();
|
||||||
let contact = Contact::get_by_id(context, invite.contact_id()).await?;
|
let contact = Contact::get_by_id(context, invite.contact_id()).await?;
|
||||||
let recipient = contact.get_addr();
|
let recipient = contact.get_addr();
|
||||||
let attach_self_pubkey = false;
|
let attach_self_pubkey = false;
|
||||||
let rendered_message = mimefactory::render_symm_encrypted_securejoin_message(
|
let rendered_message = mimefactory::render_symm_encrypted_securejoin_message(
|
||||||
context,
|
context,
|
||||||
step.securejoin_header(invite),
|
"vc-request-pubkey",
|
||||||
&rfc724_mid,
|
&rfc724_mid,
|
||||||
attach_self_pubkey,
|
attach_self_pubkey,
|
||||||
invite.authcode(),
|
invite.authcode(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
// TODO code duplication
|
let msg_id = message::insert_tombstone(context, &rfc724_mid).await?;
|
||||||
context
|
insert_into_smtp(context, &rfc724_mid, recipient, rendered_message, msg_id).await?;
|
||||||
.sql
|
|
||||||
.execute(
|
|
||||||
"INSERT INTO smtp (rfc724_mid, recipients, mime, msg_id)
|
|
||||||
VALUES (?1, ?2, ?3, ?4)",
|
|
||||||
(
|
|
||||||
&rfc724_mid,
|
|
||||||
&recipient,
|
|
||||||
&rendered_message,
|
|
||||||
0, // TODO
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
context.scheduler.interrupt_smtp().await;
|
context.scheduler.interrupt_smtp().await;
|
||||||
} else {
|
} else {
|
||||||
let mut msg = Message {
|
let mut msg = Message {
|
||||||
|
|||||||
11
src/token.rs
11
src/token.rs
@@ -62,6 +62,17 @@ pub async fn lookup(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn lookup_all(context: &Context, namespace: Namespace) -> Result<Vec<String>> {
|
||||||
|
context
|
||||||
|
.sql
|
||||||
|
.query_map_vec(
|
||||||
|
"SELECT token FROM tokens WHERE namespc=? ORDER BY timestamp DESC LIMIT 1",
|
||||||
|
(namespace,),
|
||||||
|
|row| Ok(row.get(0)?),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn lookup_or_new(
|
pub async fn lookup_or_new(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
namespace: Namespace,
|
namespace: Namespace,
|
||||||
|
|||||||
Reference in New Issue
Block a user