[fractal/fractal-next] verification: Improve the code for identity verifications



commit b2c9b70673b94917f957de562db68cc356baf61e
Author: Julian Sparber <julian sparber net>
Date:   Thu Dec 16 16:09:44 2021 +0100

    verification: Improve the code for identity verifications
    
    This makes sure that we track verifications correctly. And makes the
    code more readable.
    
    This also prepares it for user verifcation other then ourselfs

 src/session/mod.rs                                |   2 +-
 src/session/user.rs                               |  18 +
 src/session/verification/identity_verification.rs | 820 ++++++++++++----------
 src/session/verification/incoming_verification.rs |  11 +-
 src/session/verification/mod.rs                   |   9 +-
 src/session/verification/session_verification.rs  |  11 +-
 src/session/verification/verification_list.rs     | 141 ++--
 7 files changed, 580 insertions(+), 432 deletions(-)
---
diff --git a/src/session/mod.rs b/src/session/mod.rs
index 295f8592..c78e0647 100644
--- a/src/session/mod.rs
+++ b/src/session/mod.rs
@@ -523,7 +523,7 @@ impl Session {
 
                 priv_.logout_on_dispose.set(true);
 
-                let verification = IdentityVerification::new(obj.user().unwrap());
+                let verification = IdentityVerification::create(&obj, obj.user().unwrap()).await;
                 let session = SessionVerification::new(&verification, &obj);
                 obj.verification_list().add(verification);
                 priv_
diff --git a/src/session/user.rs b/src/session/user.rs
index 1bcac1c9..28c72119 100644
--- a/src/session/user.rs
+++ b/src/session/user.rs
@@ -2,6 +2,10 @@ use gtk::{glib, prelude::*, subclass::prelude::*};
 use matrix_sdk::ruma::identifiers::{MxcUri, UserId};
 
 use crate::session::{Avatar, Session};
+use crate::spawn_tokio;
+use matrix_sdk::encryption::identities::UserIdentity;
+
+use log::error;
 
 mod imp {
     use super::*;
@@ -119,6 +123,20 @@ impl User {
         glib::Object::new(&[("session", session), ("user-id", &user_id.as_str())])
             .expect("Failed to create User")
     }
+
+    pub async fn crypto_identity(&self) -> Option<UserIdentity> {
+        let client = self.session().client();
+        let user_id = self.user_id().to_owned();
+        let handle = spawn_tokio!(async move { client.get_user_identity(&user_id).await });
+
+        match handle.await.unwrap() {
+            Ok(identity) => identity,
+            Err(error) => {
+                error!("Failed to find crypto identity: {}", error);
+                None
+            }
+        }
+    }
 }
 
 pub trait UserExt: IsA<User> {
diff --git a/src/session/verification/identity_verification.rs 
b/src/session/verification/identity_verification.rs
index 084e94b8..1e015c09 100644
--- a/src/session/verification/identity_verification.rs
+++ b/src/session/verification/identity_verification.rs
@@ -1,4 +1,5 @@
 use crate::session::user::UserExt;
+use crate::session::Session;
 use crate::session::User;
 use crate::spawn;
 use crate::spawn_tokio;
@@ -10,63 +11,21 @@ use log::warn;
 use matrix_sdk::{
     encryption::{
         identities::RequestVerificationError,
-        verification::{
-            CancelInfo, Emoji, QrVerification, QrVerificationData, SasVerification,
-            Verification as MatrixVerification, VerificationRequest,
-        },
+        verification::{CancelInfo, Emoji, QrVerificationData, VerificationRequest},
     },
     ruma::{
-        api::client::r0::sync::sync_events::ToDevice,
         events::key::verification::{cancel::CancelCode, VerificationMethod},
-        events::AnyToDeviceEvent,
         identifiers::UserId,
     },
-    Client, Error as MatrixError,
+    Client,
 };
 use qrcode::QrCode;
 use tokio::sync::mpsc;
 
-#[derive(Debug, Eq, PartialEq, Clone, Copy)]
-pub enum State {
-    Request,
-    Ready,
-    Start,
-    Cancel,
-    Accept,
-    Key,
-    Mac,
-    Done,
-}
-
-impl Default for State {
-    fn default() -> Self {
-        Self::Request
-    }
-}
-
-#[derive(Debug, Clone)]
-pub enum Verification {
-    SasV1(SasVerification),
-    QrV1(QrVerification),
-    Request(VerificationRequest),
-}
-
-impl Verification {
-    fn cancel_info(&self) -> Option<CancelInfo> {
-        match self {
-            Verification::QrV1(verification) => verification.cancel_info(),
-            Verification::SasV1(verification) => verification.cancel_info(),
-            Verification::Request(verification) => verification.cancel_info(),
-        }
-    }
-}
-
 #[derive(Debug, Eq, PartialEq, Clone, Copy, glib::GEnum)]
 #[repr(u32)]
 #[genum(type_name = "VerificationMode")]
 pub enum Mode {
-    IdentityNotFound,
-    Unavailable,
     Requested,
     SasV1,
     QrV1Show,
@@ -74,6 +33,7 @@ pub enum Mode {
     Completed,
     Cancelled,
     Dismissed,
+    Passive,
     Error,
 }
 
@@ -83,6 +43,31 @@ impl Default for Mode {
     }
 }
 
+#[glib::gflags("VerificationSupportedMethods")]
+pub enum SupportedMethods {
+    NONE = 0b00000000,
+    SAS = 0b00000001,
+    QR_SHOW = 0b00000010,
+    QR_SCAN = 0b00000100,
+}
+
+impl From<VerificationMethod> for SupportedMethods {
+    fn from(method: VerificationMethod) -> Self {
+        match method {
+            VerificationMethod::SasV1 => Self::SAS,
+            VerificationMethod::QrCodeScanV1 => Self::QR_SHOW,
+            VerificationMethod::QrCodeShowV1 => Self::QR_SCAN,
+            _ => Self::NONE,
+        }
+    }
+}
+
+impl Default for SupportedMethods {
+    fn default() -> Self {
+        Self::NONE
+    }
+}
+
 #[derive(Debug, PartialEq, Clone)]
 pub enum UserAction {
     Match,
@@ -95,25 +80,41 @@ pub enum UserAction {
 #[derive(Debug, PartialEq)]
 pub enum Message {
     UserAction(UserAction),
-    Sync((String, State)),
+    NotifyState,
+}
+
+pub enum MainMessage {
+    QrCode(QrCode),
+    SasData(SasData),
+    SupportedMethods(SupportedMethods),
+    CancelInfo(CancelInfo),
+    Mode(Mode),
+}
+
+#[derive(Debug)]
+pub enum SasData {
+    Emoji([Emoji; 7]),
+    Decimal((u16, u16, u16)),
 }
 
 mod imp {
     use super::*;
     use glib::object::WeakRef;
-    use glib::source::SourceId;
     use once_cell::{sync::Lazy, unsync::OnceCell};
     use std::cell::{Cell, RefCell};
 
-    #[derive(Debug, Default)]
+    #[derive(Default)]
     pub struct IdentityVerification {
-        pub user: OnceCell<WeakRef<User>>,
+        pub user: OnceCell<User>,
+        pub session: OnceCell<WeakRef<Session>>,
         pub mode: Cell<Mode>,
+        pub supported_methods: Cell<SupportedMethods>,
         pub sync_sender: RefCell<Option<mpsc::Sender<Message>>>,
-        pub main_sender: OnceCell<glib::SyncSender<(Verification, Mode)>>,
-        pub request: RefCell<Option<Verification>>,
-        pub source_id: RefCell<Option<SourceId>>,
-        pub flow_id: RefCell<Option<String>>,
+        pub main_sender: RefCell<Option<glib::SyncSender<MainMessage>>>,
+        pub sas_data: OnceCell<SasData>,
+        pub qr_code: OnceCell<QrCode>,
+        pub cancel_info: OnceCell<CancelInfo>,
+        pub flow_id: OnceCell<String>,
     }
 
     #[glib::object_subclass]
@@ -134,6 +135,13 @@ mod imp {
                         User::static_type(),
                         glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
                     ),
+                    glib::ParamSpec::new_object(
+                        "session",
+                        "Session",
+                        "The current session",
+                        Session::static_type(),
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
+                    ),
                     glib::ParamSpec::new_enum(
                         "mode",
                         "Mode",
@@ -142,6 +150,14 @@ mod imp {
                         Mode::default() as i32,
                         glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
                     ),
+                    glib::ParamSpec::new_flags(
+                        "supported-methods",
+                        "Supported Methods",
+                        "The supported methods of this verification",
+                        SupportedMethods::static_type(),
+                        SupportedMethods::default().bits(),
+                        glib::ParamFlags::READABLE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                    ),
                     glib::ParamSpec::new_string(
                         "display-name",
                         "Display name",
@@ -154,7 +170,7 @@ mod imp {
                         "Flow Id",
                         "The flow id of this verification request",
                         None,
-                        glib::ParamFlags::READWRITE | glib::ParamFlags::EXPLICIT_NOTIFY,
+                        glib::ParamFlags::READWRITE | glib::ParamFlags::CONSTRUCT_ONLY,
                     ),
                 ]
             });
@@ -171,6 +187,7 @@ mod imp {
         ) {
             match pspec.name() {
                 "user" => obj.set_user(value.get().unwrap()),
+                "session" => obj.set_session(value.get().unwrap()),
                 "flow-id" => obj.set_flow_id(value.get().unwrap()),
                 _ => unimplemented!(),
             }
@@ -179,9 +196,11 @@ mod imp {
         fn property(&self, obj: &Self::Type, _id: usize, pspec: &glib::ParamSpec) -> glib::Value {
             match pspec.name() {
                 "user" => obj.user().to_value(),
+                "session" => obj.session().to_value(),
                 "mode" => obj.mode().to_value(),
                 "display-name" => obj.display_name().to_value(),
                 "flow-id" => obj.flow_id().to_value(),
+                "supported-methods" => obj.supported_methods().to_value(),
                 _ => unimplemented!(),
             }
         }
@@ -190,27 +209,39 @@ mod imp {
             self.parent_constructed(obj);
 
             let (main_sender, main_receiver) =
-                glib::MainContext::sync_channel::<(Verification, Mode)>(Default::default(), 100);
+                glib::MainContext::sync_channel::<MainMessage>(Default::default(), 100);
 
-            let source_id = main_receiver.attach(
+            main_receiver.attach(
                 None,
-                clone!(@weak obj => @default-return glib::Continue(false), move |(verification, mode)| {
-                    obj.set_request(Some(verification));
-                    obj.set_mode(mode);
+                clone!(@weak obj => @default-panic, move |message| {
+                    let priv_ = imp::IdentityVerification::from_instance(&obj);
+                    match message {
+                        MainMessage::QrCode(data) => { let _ = priv_.qr_code.set(data); },
+                        MainMessage::CancelInfo(data) => priv_.cancel_info.set(data).unwrap(),
+                        MainMessage::SasData(data) => priv_.sas_data.set(data).unwrap(),
+                        MainMessage::SupportedMethods(flags) => priv_.supported_methods.set(flags),
+                        MainMessage::Mode(mode) => obj.set_mode(mode),
+                    }
 
                     glib::Continue(true)
                 }),
             );
 
-            self.main_sender.set(main_sender).unwrap();
-            self.source_id.replace(Some(source_id));
+            self.main_sender.replace(Some(main_sender));
+
+            // We don't need to track ourselfs because we show "Login Request" as name in that case.
+            if obj.user() != obj.session().user().unwrap() {
+                obj.user().connect_notify_local(
+                    Some("display-name"),
+                    clone!(@weak obj => move |_, _| {
+                        obj.notify("display-name");
+                    }),
+                );
+            }
         }
 
         fn dispose(&self, obj: &Self::Type) {
             obj.cancel();
-            if let Some(source_id) = self.source_id.take() {
-                let _ = glib::Source::remove(source_id);
-            }
         }
     }
 }
@@ -220,26 +251,77 @@ glib::wrapper! {
 }
 
 impl IdentityVerification {
-    pub fn new(user: &User) -> Self {
-        glib::Object::new(&[("user", user)]).expect("Failed to create IdentityVerification")
+    fn for_mode(mode: Mode, session: &Session, user: &User) -> Self {
+        glib::Object::new(&[("mode", &mode), ("session", session), ("user", user)])
+            .expect("Failed to create IdentityVerification")
+    }
+
+    /// Create a new object tracking an already existing verification request
+    pub fn for_flow_id(flow_id: &str, session: &Session, user: &User) -> Self {
+        glib::Object::new(&[("flow-id", &flow_id), ("session", session), ("user", user)])
+            .expect("Failed to create IdentityVerification")
+    }
+
+    /// Creates and send a new verificaiton request
+    pub async fn create(session: &Session, user: &User) -> Self {
+        if let Some(identity) = user.crypto_identity().await {
+            let handle = spawn_tokio!(async move {
+                identity
+                    .request_verification_with_methods(vec![
+                        VerificationMethod::SasV1,
+                        VerificationMethod::QrCodeScanV1,
+                        VerificationMethod::QrCodeShowV1,
+                        VerificationMethod::ReciprocateV1,
+                    ])
+                    .await
+            });
+
+            match handle.await.unwrap() {
+                Ok(request) => {
+                    let obj = Self::for_flow_id(request.flow_id(), session, user);
+                    // This will start the request handling
+                    obj.accept();
+                    return obj;
+                }
+                Err(error) => {
+                    error!("Starting a verification failed: {}", error);
+                }
+            }
+        } else {
+            error!("Starting a verification failed: Crypto identity wasn't found");
+        }
+
+        Self::for_mode(Mode::Error, session, user)
     }
 
-    pub fn accept_incoming(&self) {
+    /// Accept an incomming request
+    pub fn accept(&self) {
         let priv_ = imp::IdentityVerification::from_instance(self);
-        let user = self.user();
-        let client = user.session().client();
-        let user_id = user.user_id().clone();
-        let main_sender = priv_.main_sender.get().unwrap().clone();
-        let flow_id = self.flow_id().clone().unwrap();
 
-        self.set_request(None);
+        let main_sender = if let Some(main_sender) = priv_.main_sender.take() {
+            main_sender
+        } else {
+            warn!("The verification request was already started");
+            return;
+        };
+
+        let client = self.session().client();
+        let user_id = self.user().user_id().to_owned();
+        let flow_id = self.flow_id().to_owned();
+
         let (sync_sender, sync_receiver) = mpsc::channel(100);
         priv_.sync_sender.replace(Some(sync_sender));
 
         // TODO add timeout
 
         let handle = spawn_tokio!(async move {
-            start(client, user_id, flow_id, main_sender, sync_receiver).await
+            if let Some(context) =
+                Context::new(client, &user_id, &flow_id, main_sender, sync_receiver).await
+            {
+                context.start().await
+            } else {
+                Ok(Mode::Error)
+            }
         });
 
         let weak_obj = self.downgrade();
@@ -249,75 +331,42 @@ impl IdentityVerification {
                 let priv_ = imp::IdentityVerification::from_instance(&obj);
                 match result {
                     Ok(result) => obj.set_mode(result),
-                    Err(error) => error!("Verification failed: {}", error),
+                    Err(error) => {
+                        // FIXME: report error to the user
+                        error!("Verification failed: {}", error);
+                        obj.set_mode(Mode::Error);
+                    }
                 }
                 priv_.sync_sender.take();
             }
         });
     }
 
-    pub fn user(&self) -> User {
+    /// The user to be verified.
+    pub fn user(&self) -> &User {
         let priv_ = imp::IdentityVerification::from_instance(self);
-        priv_.user.get().unwrap().upgrade().unwrap()
+        priv_.user.get().unwrap()
     }
 
     fn set_user(&self, user: User) {
         let priv_ = imp::IdentityVerification::from_instance(self);
-        priv_.user.set(user.downgrade()).unwrap()
+        priv_.user.set(user).unwrap()
     }
 
-    /// Start an interactive identity verification
-    /// Already in progress verifications are cancelled before starting a new one
-    pub fn start(&self) {
+    /// The current `Session`.
+    pub fn session(&self) -> Session {
         let priv_ = imp::IdentityVerification::from_instance(self);
-        let user = self.user();
-        let client = user.session().client();
-        let user_id = user.user_id().clone();
-        let main_sender = priv_.main_sender.get().unwrap().clone();
-
-        self.set_request(None);
-        // TODO cancel any other request in progress
-
-        let (sync_sender, sync_receiver) = mpsc::channel(100);
-        priv_.sync_sender.replace(Some(sync_sender));
-
-        // TODO add timeout
-
-        let handle = spawn_tokio!(async move {
-            let identity = if let Some(identity) =
-                client.get_user_identity(&user_id).await.map_err(|error| {
-                    RequestVerificationError::Sdk(MatrixError::CryptoStoreError(error))
-                })? {
-                identity
-            } else {
-                return Ok(Mode::IdentityNotFound);
-            };
-
-            let request = identity
-                .request_verification_with_methods(vec![
-                    VerificationMethod::SasV1,
-                    VerificationMethod::QrCodeScanV1,
-                    VerificationMethod::QrCodeShowV1,
-                    VerificationMethod::ReciprocateV1,
-                ])
-                .await?;
-            let flow_id = request.flow_id().to_owned();
+        priv_.session.get().unwrap().upgrade().unwrap()
+    }
 
-            start(client, user_id, flow_id, main_sender, sync_receiver).await
-        });
+    fn set_session(&self, session: Session) {
+        let priv_ = imp::IdentityVerification::from_instance(self);
+        priv_.session.set(session.downgrade()).unwrap()
+    }
 
-        let weak_obj = self.downgrade();
-        spawn!(async move {
-            let result = handle.await.unwrap();
-            if let Some(obj) = weak_obj.upgrade() {
-                let priv_ = imp::IdentityVerification::from_instance(&obj);
-                match result {
-                    Ok(result) => obj.set_mode(result),
-                    Err(error) => error!("Verification failed: {}", error),
-                }
-                priv_.sync_sender.take();
-            }
-        });
+    fn supported_methods(&self) -> SupportedMethods {
+        let priv_ = imp::IdentityVerification::from_instance(self);
+        priv_.supported_methods.get()
     }
 
     pub fn emoji_match(&self) {
@@ -337,6 +386,7 @@ impl IdentityVerification {
 
         if let Some(sync_sender) = &*priv_.sync_sender.borrow() {
             let result = sync_sender.try_send(Message::UserAction(UserAction::NotMatch));
+
             if let Err(error) = result {
                 error!("Failed to send message to tokio runtime: {}", error);
             }
@@ -356,15 +406,7 @@ impl IdentityVerification {
         }
 
         match mode {
-            Mode::SasV1 => {
-                if self.emoji().is_none() {
-                    warn!("Failed to get emoji for SasVerification");
-                    self.show_error();
-                }
-            }
-            Mode::Unavailable | Mode::Cancelled => {
-                self.show_error();
-            }
+            Mode::Cancelled | Mode::Error => self.show_error(),
             _ => {}
         }
 
@@ -372,14 +414,15 @@ impl IdentityVerification {
         self.notify("mode");
     }
 
-    fn set_request(&self, request: Option<Verification>) {
-        let priv_ = imp::IdentityVerification::from_instance(self);
-
-        priv_.request.replace(request);
+    /// Whether this request is finished
+    pub fn is_finished(&self) -> bool {
+        matches!(
+            self.mode(),
+            Mode::Error | Mode::Cancelled | Mode::Dismissed | Mode::Completed | Mode::Passive
+        )
     }
 
     fn show_error(&self) {
-        self.set_mode(Mode::Error);
         let error_message = if let Some(info) = self.cancel_info() {
             match info.cancel_code() {
                 CancelCode::User => Some(gettext("You cancelled the verificaiton process.")),
@@ -410,52 +453,52 @@ impl IdentityVerification {
             Some(error_label.upcast())
         });
 
-        if let Some(window) = self.user().session().parent_window() {
+        if let Some(window) = self.session().parent_window() {
             window.append_error(&error);
         }
     }
 
     pub fn display_name(&self) -> String {
-        // TODO: give this request a name based on the device
-        "Request".to_string()
+        if self.user() != self.session().user().unwrap() {
+            self.user().display_name()
+        } else {
+            // TODO: give this request a name based on the device
+            "Login Request".to_string()
+        }
     }
 
-    pub fn flow_id(&self) -> Option<String> {
+    pub fn flow_id(&self) -> &str {
         let priv_ = imp::IdentityVerification::from_instance(self);
-        priv_.flow_id.borrow().clone()
+        priv_
+            .flow_id
+            .get()
+            .expect("Flow Id isn't always set on verifications with error state.")
     }
 
-    pub fn set_flow_id(&self, flow_id: Option<String>) {
+    fn set_flow_id(&self, flow_id: String) {
         let priv_ = imp::IdentityVerification::from_instance(self);
 
-        priv_.flow_id.replace(flow_id);
-        self.notify("flow-id");
+        priv_.flow_id.set(flow_id).unwrap();
     }
 
     /// Get the QrCode for this verification request
     ///
     /// This is only set once the request reached the `State::Ready`
     /// and if QrCode verification is possible
-    pub fn qr_code(&self) -> Option<QrCode> {
+    pub fn qr_code(&self) -> Option<&QrCode> {
         let priv_ = imp::IdentityVerification::from_instance(self);
 
-        match &*priv_.request.borrow() {
-            Some(Verification::QrV1(qr_verification)) => qr_verification.to_qr_code().ok(),
-            _ => None,
-        }
+        priv_.qr_code.get()
     }
 
     /// Get the Emojis for this verification request
     ///
     /// This is only set once the request reached the `State::Ready`
     /// and if a Sas verification was started
-    pub fn emoji(&self) -> Option<[Emoji; 7]> {
+    pub fn sas_data(&self) -> Option<&SasData> {
         let priv_ = imp::IdentityVerification::from_instance(self);
 
-        match &*priv_.request.borrow() {
-            Some(Verification::SasV1(qr_verification)) => qr_verification.emoji(),
-            _ => None,
-        }
+        priv_.sas_data.get()
     }
 
     pub fn start_sas(&self) {
@@ -497,271 +540,296 @@ impl IdentityVerification {
     }
 
     /// Get information about why the request was cancelled
-    pub fn cancel_info(&self) -> Option<CancelInfo> {
+    pub fn cancel_info(&self) -> Option<&CancelInfo> {
         let priv_ = imp::IdentityVerification::from_instance(self);
 
-        if let Some(verification) = &*priv_.request.borrow() {
-            verification.cancel_info()
-        } else {
-            None
-        }
+        priv_.cancel_info.get()
     }
 
-    pub fn handle_response_to_device(&self, to_device: ToDevice) {
+    pub fn notify_state(&self) {
         let priv_ = imp::IdentityVerification::from_instance(self);
 
-        for event in to_device.events.iter().filter_map(|e| e.deserialize().ok()) {
-            let (flow_id, state) = match event {
-                AnyToDeviceEvent::KeyVerificationRequest(e) => {
-                    (e.content.transaction_id, State::Request)
-                }
-                AnyToDeviceEvent::KeyVerificationReady(e) => {
-                    (e.content.transaction_id, State::Ready)
-                }
-                AnyToDeviceEvent::KeyVerificationStart(e) => {
-                    (e.content.transaction_id, State::Start)
-                }
-                AnyToDeviceEvent::KeyVerificationCancel(e) => {
-                    (e.content.transaction_id, State::Cancel)
-                }
-                AnyToDeviceEvent::KeyVerificationAccept(e) => {
-                    (e.content.transaction_id, State::Accept)
-                }
-                AnyToDeviceEvent::KeyVerificationMac(e) => (e.content.transaction_id, State::Mac),
-                AnyToDeviceEvent::KeyVerificationKey(e) => (e.content.transaction_id, State::Key),
-                AnyToDeviceEvent::KeyVerificationDone(e) => (e.content.transaction_id, State::Done),
-                _ => continue,
-            };
-
-            if let Some(sync_sender) = &*priv_.sync_sender.borrow() {
-                let result = sync_sender.try_send(Message::Sync((flow_id, state)));
-                if let Err(error) = result {
-                    error!("Failed to send message to tokio runtime: {}", error);
-                }
+        if let Some(sync_sender) = &*priv_.sync_sender.borrow() {
+            let result = sync_sender.try_send(Message::NotifyState);
+            if let Err(error) = result {
+                error!("Failed to send message to tokio runtime: {}", error);
             }
         }
     }
 }
 
-async fn start(
-    client: Client,
-    user_id: UserId,
-    flow_id: String,
-    main_sender: glib::SyncSender<(Verification, Mode)>,
-    mut sync_receiver: mpsc::Receiver<Message>,
-) -> Result<Mode, RequestVerificationError> {
-    let request =
-        if let Some(verification) = client.get_verification_request(&user_id, &flow_id).await {
-            verification
-        } else {
-            return Ok(Mode::Unavailable);
-        };
-
-    let result = main_sender.send((Verification::Request(request.clone()), Mode::Requested));
-
-    if let Err(error) = result {
-        error!("Failed to send message to the main context: {}", error);
-    }
-
-    if !request.we_started() {
-        request
-            .accept_with_methods(vec![
-                VerificationMethod::SasV1,
-                VerificationMethod::QrCodeScanV1,
-                VerificationMethod::QrCodeShowV1,
-                VerificationMethod::ReciprocateV1,
-            ])
-            .await?;
-    } else {
-        if wait_for_state(&flow_id, State::Ready, &mut sync_receiver).await {
-            request.cancel().await?;
-            return Ok(Mode::Cancelled);
-        }
-    }
-
-    let supports_qr_code_scanning = request.their_supported_methods().map_or(false, |methods| {
-        methods
-            .iter()
-            .any(|method| method == &VerificationMethod::QrCodeScanV1)
-    });
-
-    let qr_verification = if supports_qr_code_scanning {
-        request
-            .generate_qr_code()
-            .await
-            .map_err(|error| RequestVerificationError::Sdk(error))?
-    } else {
-        None
-    };
-
-    let start_sas = if let Some(qr_verification) = qr_verification {
-        let result = main_sender.send((Verification::QrV1(qr_verification), Mode::QrV1Show));
-
-        if let Err(error) = result {
-            error!("Failed to send message to the main context: {}", error);
-        }
+struct Context {
+    main_sender: glib::SyncSender<MainMessage>,
+    sync_receiver: mpsc::Receiver<Message>,
+    request: VerificationRequest,
+}
 
-        loop {
-            match sync_receiver.recv().await.unwrap() {
-                Message::Sync((id, State::Start)) if flow_id == id => break false,
-                Message::Sync((id, State::Cancel)) if flow_id == id => {
-                    request.cancel().await?;
-                    return Ok(Mode::Cancelled);
-                }
-                Message::UserAction(UserAction::Cancel) => {
-                    request.cancel().await?;
-                    return Ok(Mode::Cancelled);
+macro_rules! wait {
+    ( $this:ident $(, $expected:expr )? $(; expect_match $allow_action:ident )? ) => {
+        {
+            loop {
+                match $this.sync_receiver.recv().await.expect("The channel was closed unexpected") {
+                    Message::NotifyState if $this.request.is_cancelled() => {
+                        if let Some(info) = $this.request.cancel_info() {
+                            $this.send_cancel_info(info);
+                        }
+                        return Ok(Mode::Cancelled);
+                    },
+                    Message::UserAction(UserAction::Cancel) | Message::UserAction(UserAction::NotMatch) => {
+                        return Ok($this.cancel_request().await?);
+                    }
+                    Message::UserAction(UserAction::StartSas) => {
+                        if true $(&& $allow_action)? {
+                            return Ok($this.start_sas().await?);
+                        }
+                    },
+                    Message::UserAction(UserAction::Match) => {
+                        if $this.request.is_passive() {
+                            return Ok(Mode::Passive);
+                        }
+
+                        // Break only if we are in the expected state
+                        if true $(&& $expected)? {
+                            break;
+                        }
+                    },
+                    Message::UserAction(UserAction::Scanned(data)) => {
+                        if true $(&& $allow_action)? {
+                            return Ok($this.finish_scanning(data).await?);
+                        }
+                    },
+                    Message::NotifyState => {
+                    }
                 }
-                Message::UserAction(UserAction::StartSas) => break true,
-                Message::UserAction(UserAction::Scanned(data)) => {
-                    request.scan_qr_code(data).await?;
-                    break false;
+
+                if $this.request.is_passive() {
+                    return Ok(Mode::Passive);
                 }
-                _ => {}
-            }
-        }
-    } else {
-        let supports_qr_code_showing = request.their_supported_methods().map_or(false, |methods| {
-            methods
-                .iter()
-                .any(|method| method == &VerificationMethod::QrCodeShowV1)
-        });
-        if supports_qr_code_showing {
-            let result = main_sender.send((Verification::Request(request.clone()), Mode::QrV1Scan));
 
-            if let Err(error) = result {
-                error!("Failed to send message to the main context: {}", error);
+                $(
+                    if $expected {
+                        break;
+                    }
+                )?
             }
+        }
+    };
+}
 
+// WORKAROUND: since rust thinks that we are creating a recursive async function
+macro_rules! wait_without_scanning_sas {
+    ( $this:ident $(, $expected:expr )?) => {
+        {
             loop {
-                match sync_receiver.recv().await.unwrap() {
-                    Message::Sync((id, State::Start)) if flow_id == id => break false,
-                    Message::Sync((id, State::Cancel)) if flow_id == id => {
-                        request.cancel().await?;
+                match $this.sync_receiver.recv().await.expect("The channel was closed unexpected") {
+                    Message::NotifyState if $this.request.is_cancelled() => {
+                        if let Some(info) = $this.request.cancel_info() {
+                            $this.send_cancel_info(info);
+                        }
                         return Ok(Mode::Cancelled);
-                    }
+                    },
                     Message::UserAction(UserAction::Cancel) => {
-                        request.cancel().await?;
-                        return Ok(Mode::Cancelled);
+                        return Ok($this.cancel_request().await?);
                     }
-                    Message::UserAction(UserAction::StartSas) => break true,
-                    Message::UserAction(UserAction::Scanned(data)) => {
-                        request.scan_qr_code(data).await?;
-                        break false;
+                    Message::UserAction(UserAction::NotMatch) => {
+                        return Ok($this.cancel_request().await?);
+                    },
+                    Message::UserAction(UserAction::StartSas) => {
+                    },
+                    Message::UserAction(UserAction::Match) => {
+                        if $this.request.is_passive() {
+                            return Ok(Mode::Passive);
+                        }
+
+                        // Break only if we are in the expected state
+                        if true $(&& $expected)? {
+                            break;
+                        }
+                    },
+                    Message::UserAction(UserAction::Scanned(_)) => {
+                    },
+                    Message::NotifyState => {
                     }
-                    _ => {}
                 }
-            }
-        } else {
-            true
-        }
-    };
 
-    if start_sas {
-        if request
-            .start_sas()
-            .await
-            .map_err(|error| RequestVerificationError::Sdk(error))?
-            .is_some()
-        {
-            let cancel = loop {
-                match sync_receiver.recv().await {
-                    Some(Message::Sync((id, State::Start))) if flow_id == id => break false,
-                    Some(Message::Sync((id, State::Accept))) if flow_id == id => break false,
-                    Some(Message::Sync((id, State::Cancel))) if flow_id == id => break true,
-                    Some(Message::UserAction(UserAction::Cancel)) => break true,
-                    None => break true,
-                    _ => {}
+                if $this.request.is_passive() {
+                    return Ok(Mode::Passive);
                 }
-            };
 
-            if cancel {
-                request.cancel().await?;
-                return Ok(Mode::Cancelled);
+                $(
+                    if $expected {
+                        break;
+                    }
+                )?
             }
-        } else {
-            return Ok(Mode::Unavailable);
         }
+    };
+}
+
+impl Context {
+    pub async fn new(
+        client: Client,
+        user_id: &UserId,
+        flow_id: &str,
+        main_sender: glib::SyncSender<MainMessage>,
+        sync_receiver: mpsc::Receiver<Message>,
+    ) -> Option<Self> {
+        let request = client.get_verification_request(user_id, flow_id).await?;
+
+        Some(Self {
+            request,
+            main_sender,
+            sync_receiver,
+        })
     }
 
-    // Get the verification struct from the sdk, this way we are sure we get the correct type
-    let verification = if let Some(verification) = client.get_verification(&user_id, &flow_id).await
-    {
-        verification
-    } else {
-        return Ok(Mode::Unavailable);
-    };
+    fn send_mode(&self, mode: Mode) {
+        self.main_sender.send(MainMessage::Mode(mode)).unwrap();
+    }
+
+    fn send_qr_code(&self, qr_code: QrCode) {
+        self.main_sender.send(MainMessage::QrCode(qr_code)).unwrap();
+    }
+
+    fn send_sas_data(&self, data: SasData) {
+        self.main_sender.send(MainMessage::SasData(data)).unwrap();
+    }
+
+    fn send_cancel_info(&self, cancel_info: CancelInfo) {
+        self.main_sender
+            .send(MainMessage::CancelInfo(cancel_info))
+            .unwrap();
+    }
 
-    match verification {
-        MatrixVerification::QrV1(qr_verification) => {
-            qr_verification.confirm().await?;
+    fn send_supported_methods(&self, flags: SupportedMethods) {
+        self.main_sender
+            .send(MainMessage::SupportedMethods(flags))
+            .unwrap();
+    }
 
-            if wait_for_state(&flow_id, State::Done, &mut sync_receiver).await {
-                request.cancel().await?;
-                return Ok(Mode::Cancelled);
+    async fn start(mut self) -> Result<Mode, RequestVerificationError> {
+        if self.request.we_started() {
+            wait![self, self.request.is_ready()];
+        } else {
+            // Check if it was started by somebody else already
+            if self.request.is_passive() {
+                return Ok(Mode::Passive);
             }
+
+            self.request
+                .accept_with_methods(vec![
+                    VerificationMethod::SasV1,
+                    VerificationMethod::QrCodeScanV1,
+                    VerificationMethod::QrCodeShowV1,
+                    VerificationMethod::ReciprocateV1,
+                ])
+                .await?;
         }
-        MatrixVerification::SasV1(sas_verification) => {
-            sas_verification.accept().await?;
 
-            if wait_for_state(&flow_id, State::Key, &mut sync_receiver).await {
-                request.cancel().await?;
-                return Ok(Mode::Cancelled);
+        let supported_methods: SupportedMethods = self
+            .request
+            .their_supported_methods()
+            .unwrap()
+            .into_iter()
+            .map(Into::into)
+            .collect();
+
+        self.send_supported_methods(supported_methods);
+
+        let request = if supported_methods.contains(SupportedMethods::QR_SHOW) {
+            let request = self
+                .request
+                .generate_qr_code()
+                .await
+                .map_err(|error| RequestVerificationError::Sdk(error))?
+                .expect("Couldn't create qr-code");
+
+            if let Ok(qr_code) = request.to_qr_code() {
+                self.send_qr_code(qr_code);
+            } else {
+                return Ok(Mode::Error);
             }
 
-            let result =
-                main_sender.send((Verification::SasV1(sas_verification.clone()), Mode::SasV1));
+            self.send_mode(Mode::QrV1Show);
 
-            if let Err(error) = result {
-                error!("Failed to send message to the main context: {}", error);
-            }
+            request
+        } else if supported_methods.contains(SupportedMethods::QR_SCAN) {
+            self.send_mode(Mode::QrV1Scan);
 
-            if wait_for_match_action(&flow_id, &mut sync_receiver).await {
-                request.cancel().await?;
-                return Ok(Mode::Cancelled);
-            }
+            // Wait for scanned data
+            wait![self];
 
-            sas_verification.confirm().await?;
+            unreachable!();
+        } else {
+            return Ok(self.start_sas().await?);
+        };
 
-            if wait_for_state(&flow_id, State::Done, &mut sync_receiver).await {
-                request.cancel().await?;
-                return Ok(Mode::Cancelled);
-            }
-        }
+        // FIXME: we should automatically confirm
+        request.confirm().await?;
+
+        wait![self, request.is_done()];
+
+        Ok(Mode::Completed)
     }
 
-    Ok(Mode::Completed)
-}
+    async fn finish_scanning(
+        mut self,
+        data: QrVerificationData,
+    ) -> Result<Mode, RequestVerificationError> {
+        let request = self
+            .request
+            .scan_qr_code(data)
+            .await?
+            .expect("Scanning Qr Code should be supported");
 
-async fn wait_for_state(
-    flow_id: &str,
-    expected_state: State,
-    sync_receiver: &mut mpsc::Receiver<Message>,
-) -> bool {
-    loop {
-        match sync_receiver.recv().await {
-            Some(Message::Sync((id, State::Cancel))) if flow_id == &id => return true,
-            Some(Message::Sync((id, state))) if flow_id == &id && expected_state == state => break,
-            Some(Message::UserAction(UserAction::Cancel)) => return true,
-            None => return true,
-            _ => {}
-        }
+        // FIXME: we should automatically confirm
+        request.confirm().await?;
+
+        wait_without_scanning_sas![self, request.is_done()];
+
+        Ok(Mode::Completed)
     }
 
-    false
-}
+    async fn start_sas(mut self) -> Result<Mode, RequestVerificationError> {
+        let request = self
+            .request
+            .start_sas()
+            .await
+            .map_err(|error| RequestVerificationError::Sdk(error))?
+            .expect("Sas should be supported");
 
-async fn wait_for_match_action(flow_id: &str, sync_receiver: &mut mpsc::Receiver<Message>) -> bool {
-    loop {
-        match sync_receiver.recv().await {
-            Some(Message::Sync((id, State::Cancel))) if flow_id == &id => return true,
-            Some(Message::UserAction(UserAction::Match)) => break,
-            Some(Message::UserAction(UserAction::NotMatch)) => return true,
-            Some(Message::UserAction(UserAction::Cancel)) => return true,
-            None => return true,
-            _ => {}
-        }
+        request.accept().await?;
+
+        wait_without_scanning_sas![self, request.can_be_presented()];
+
+        let sas_data = if let Some(emoji) = request.emoji() {
+            SasData::Emoji(emoji)
+        } else if let Some(decimal) = request.decimals() {
+            SasData::Decimal(decimal)
+        } else {
+            return Ok(Mode::Error);
+        };
+
+        self.send_sas_data(sas_data);
+        self.send_mode(Mode::SasV1);
+
+        // Wait for match user action
+        wait_without_scanning_sas![self];
+
+        request.confirm().await?;
+
+        wait_without_scanning_sas![self, request.is_done()];
+
+        Ok(Mode::Completed)
     }
 
-    false
+    async fn cancel_request(self) -> Result<Mode, RequestVerificationError> {
+        self.request.cancel().await?;
+
+        if let Some(info) = self.request.cancel_info() {
+            self.send_cancel_info(info);
+        }
+
+        Ok(Mode::Cancelled)
+    }
 }
diff --git a/src/session/verification/incoming_verification.rs 
b/src/session/verification/incoming_verification.rs
index 30d14b08..a4bfad7e 100644
--- a/src/session/verification/incoming_verification.rs
+++ b/src/session/verification/incoming_verification.rs
@@ -7,7 +7,7 @@ use crate::contrib::screenshot;
 use crate::contrib::QRCode;
 use crate::contrib::QRCodeExt;
 use crate::contrib::QrCodeScanner;
-use crate::session::verification::{Emoji, IdentityVerification, VerificationMode};
+use crate::session::verification::{Emoji, IdentityVerification, SasData, VerificationMode};
 use crate::spawn;
 use gettextrs::gettext;
 use matrix_sdk::encryption::verification::QrVerificationData;
@@ -325,7 +325,7 @@ impl IncomingVerification {
 
     pub fn accept(&self) {
         if let Some(request) = self.request() {
-            request.accept_incoming();
+            request.accept();
         }
     }
 
@@ -339,15 +339,12 @@ impl IncomingVerification {
         let priv_ = imp::IncomingVerification::from_instance(self);
         if let Some(request) = self.request() {
             match request.mode() {
-                VerificationMode::IdentityNotFound => {
-                    // TODO: what should we do if we don't find the identity
-                }
                 VerificationMode::Requested => {
                     priv_.main_stack.set_visible_child_name("accept-request");
                 }
                 VerificationMode::QrV1Show => {
                     if let Some(qrcode) = request.qr_code() {
-                        priv_.qrcode.set_qrcode(qrcode);
+                        priv_.qrcode.set_qrcode(qrcode.clone());
                         priv_.main_stack.set_visible_child_name("qrcode");
                     } else {
                         warn!("Failed to get qrcode for QrVerification");
@@ -360,7 +357,7 @@ impl IncomingVerification {
                 VerificationMode::SasV1 => {
                     self.clean_emoji();
                     // TODO: implement sas fallback when emojis arn't supported
-                    if let Some(emoji) = request.emoji() {
+                    if let Some(SasData::Emoji(emoji)) = request.sas_data() {
                         for (index, emoji) in emoji.iter().enumerate() {
                             if index < 4 {
                                 priv_.emoji_row_1.append(&Emoji::new(emoji));
diff --git a/src/session/verification/mod.rs b/src/session/verification/mod.rs
index 489f7d8e..f886f63d 100644
--- a/src/session/verification/mod.rs
+++ b/src/session/verification/mod.rs
@@ -5,7 +5,14 @@ mod session_verification;
 mod verification_list;
 
 pub use self::emoji::Emoji;
-pub use self::identity_verification::{IdentityVerification, Mode as VerificationMode};
+pub use self::identity_verification::{IdentityVerification, Mode as VerificationMode, SasData};
 pub use self::incoming_verification::IncomingVerification;
 pub use self::session_verification::SessionVerification;
 pub use self::verification_list::VerificationList;
+
+use std::time::Duration;
+/// The time a verification is valid after it's creation.
+pub const VERIFICATION_CREATION_TIMEOUT: Duration = Duration::from_secs(60 * 10);
+/// The time a verification is valid after it was received by the client.
+#[allow(dead_code)]
+pub const VERIFICATION_RECEIVE_TIMEOUT: Duration = Duration::from_secs(60 * 2);
diff --git a/src/session/verification/session_verification.rs 
b/src/session/verification/session_verification.rs
index df7f4e06..d89df362 100644
--- a/src/session/verification/session_verification.rs
+++ b/src/session/verification/session_verification.rs
@@ -8,7 +8,7 @@ use crate::contrib::screenshot;
 use crate::contrib::QRCode;
 use crate::contrib::QRCodeExt;
 use crate::contrib::QrCodeScanner;
-use crate::session::verification::{Emoji, IdentityVerification, VerificationMode};
+use crate::session::verification::{Emoji, IdentityVerification, SasData, VerificationMode};
 use crate::session::Session;
 use crate::spawn;
 use crate::Error;
@@ -281,8 +281,6 @@ impl SessionVerification {
         );
 
         priv_.mode_handler.replace(Some(handler));
-
-        request.start();
     }
 
     /// Cancel the verification request without telling the user about it
@@ -302,7 +300,8 @@ impl SessionVerification {
         let priv_ = imp::SessionVerification::from_instance(self);
         let request = self.request();
         match request.mode() {
-            VerificationMode::IdentityNotFound => {
+            // FIXME: we bootstrap on all errors
+            VerificationMode::Error => {
                 priv_.main_stack.set_visible_child_name("bootstrap");
             }
             VerificationMode::Requested => {
@@ -310,7 +309,7 @@ impl SessionVerification {
             }
             VerificationMode::QrV1Show => {
                 if let Some(qrcode) = request.qr_code() {
-                    priv_.qrcode.set_qrcode(qrcode);
+                    priv_.qrcode.set_qrcode(qrcode.clone());
                     priv_.main_stack.set_visible_child_name("qrcode");
                 } else {
                     warn!("Failed to get qrcode for QrVerification");
@@ -322,7 +321,7 @@ impl SessionVerification {
             }
             VerificationMode::SasV1 => {
                 // TODO: implement sas fallback when emojis arn't supported
-                if let Some(emoji) = request.emoji() {
+                if let Some(SasData::Emoji(emoji)) = request.sas_data() {
                     for (index, emoji) in emoji.iter().enumerate() {
                         if index < 4 {
                             priv_.emoji_row_1.append(&Emoji::new(emoji));
diff --git a/src/session/verification/verification_list.rs b/src/session/verification/verification_list.rs
index fedb7a59..5b3290ba 100644
--- a/src/session/verification/verification_list.rs
+++ b/src/session/verification/verification_list.rs
@@ -1,13 +1,26 @@
+use crate::session::user::UserExt;
 use gtk::{gio, glib, glib::clone, prelude::*, subclass::prelude::*};
-use matrix_sdk::ruma::{api::client::r0::sync::sync_events::ToDevice, events::AnyToDeviceEvent};
-
-use crate::session::{
-    verification::{IdentityVerification, VerificationMode},
-    Session,
+use matrix_sdk::ruma::{
+    api::client::r0::sync::sync_events::ToDevice, events::AnyToDeviceEvent, identifiers::UserId,
 };
 
+use crate::session::{verification::IdentityVerification, Session};
+
+#[derive(Hash, PartialEq, Eq, Debug)]
+pub struct FlowId {
+    user_id: UserId,
+    flow_id: String,
+}
+
+impl FlowId {
+    pub fn new(user_id: UserId, flow_id: String) -> Self {
+        Self { user_id, flow_id }
+    }
+}
+
 mod imp {
     use glib::object::WeakRef;
+    use indexmap::IndexMap;
     use once_cell::{sync::Lazy, unsync::OnceCell};
     use std::cell::RefCell;
 
@@ -15,7 +28,7 @@ mod imp {
 
     #[derive(Debug, Default)]
     pub struct VerificationList {
-        pub list: RefCell<Vec<IdentityVerification>>,
+        pub list: RefCell<IndexMap<FlowId, IdentityVerification>>,
         pub session: OnceCell<WeakRef<Session>>,
     }
 
@@ -76,9 +89,8 @@ mod imp {
         fn item(&self, _list_model: &Self::Type, position: u32) -> Option<glib::Object> {
             self.list
                 .borrow()
-                .get(position as usize)
-                .map(glib::object::Cast::upcast_ref::<glib::Object>)
-                .cloned()
+                .get_index(position as usize)
+                .map(|(_, item)| item.clone().upcast::<glib::Object>())
         }
     }
 }
@@ -99,33 +111,79 @@ impl VerificationList {
     }
 
     pub fn handle_response_to_device(&self, to_device: ToDevice) {
-        let priv_ = imp::VerificationList::from_instance(self);
-
-        for event in &to_device.events {
-            if let Ok(AnyToDeviceEvent::KeyVerificationRequest(event)) = event.deserialize() {
-                let request = IdentityVerification::new(self.session().user().unwrap());
-                request.set_flow_id(Some(event.content.transaction_id.to_owned()));
-                self.add(request);
+        for event in to_device.events.iter().filter_map(|e| e.deserialize().ok()) {
+            let flow_id = match event {
+                AnyToDeviceEvent::KeyVerificationRequest(e) => {
+                    FlowId::new(e.sender, e.content.transaction_id)
+                }
+                AnyToDeviceEvent::KeyVerificationReady(e) => {
+                    FlowId::new(e.sender, e.content.transaction_id)
+                }
+                AnyToDeviceEvent::KeyVerificationStart(e) => {
+                    FlowId::new(e.sender, e.content.transaction_id)
+                }
+                AnyToDeviceEvent::KeyVerificationCancel(e) => {
+                    FlowId::new(e.sender, e.content.transaction_id)
+                }
+                AnyToDeviceEvent::KeyVerificationAccept(e) => {
+                    FlowId::new(e.sender, e.content.transaction_id)
+                }
+                AnyToDeviceEvent::KeyVerificationMac(e) => {
+                    FlowId::new(e.sender, e.content.transaction_id)
+                }
+                AnyToDeviceEvent::KeyVerificationKey(e) => {
+                    FlowId::new(e.sender, e.content.transaction_id)
+                }
+                AnyToDeviceEvent::KeyVerificationDone(e) => {
+                    FlowId::new(e.sender, e.content.transaction_id)
+                }
+                _ => continue,
+            };
+
+            if let Some(request) = self.get_by_id(&flow_id) {
+                request.notify_state();
+            } else {
+                let session = self.session();
+                let user = session.user().unwrap();
+                // ToDevice verifications can only be send by us
+                if &flow_id.user_id == user.user_id() {
+                    let request =
+                        IdentityVerification::for_flow_id(&flow_id.flow_id, &session, user);
+                    request.notify_state();
+                    self.add(request);
+                }
             }
         }
-
-        for verification in &*priv_.list.borrow() {
-            verification.handle_response_to_device(to_device.clone());
-        }
     }
 
     /// Add a new `IdentityVerification` request
     pub fn add(&self, request: IdentityVerification) {
         let priv_ = imp::VerificationList::from_instance(self);
+
+        // Don't add requests that are already finished
+        if request.is_finished() {
+            return;
+        }
+
         let length = {
             let mut list = priv_.list.borrow_mut();
             let length = list.len();
-            request.connect_notify_local(Some("mode"), clone!(@weak self as obj => move |request, _| {
-                if request.mode() == VerificationMode::Error || request.mode() == 
VerificationMode::Cancelled || request.mode() == VerificationMode::Dismissed || request.mode() == 
VerificationMode::Completed {
-                    obj.remove(request);
-                }
-            }));
-            list.push(request);
+            request.connect_notify_local(
+                Some("mode"),
+                clone!(@weak self as obj => move |request, _| {
+                    if request.is_finished() {
+                        obj.remove(request);
+                    }
+                }),
+            );
+
+            list.insert(
+                FlowId::new(
+                    request.user().user_id().to_owned(),
+                    request.flow_id().to_owned(),
+                ),
+                request,
+            );
             length as u32
         };
         self.items_changed(length, 0, 1)
@@ -133,22 +191,23 @@ impl VerificationList {
 
     pub fn remove(&self, request: &IdentityVerification) {
         let priv_ = imp::VerificationList::from_instance(self);
-        let position = {
-            let mut list = priv_.list.borrow_mut();
-            let mut position = None;
-            for (index, item) in list.iter().enumerate() {
-                if item == request {
-                    position = Some(index);
-                    break;
-                }
-            }
-            if let Some(position) = position {
-                list.remove(position);
-            }
+
+        let position = if let Some((position, _, _)) =
+            priv_.list.borrow_mut().shift_remove_full(&FlowId::new(
+                request.user().user_id().to_owned(),
+                request.flow_id().to_owned(),
+            )) {
             position
+        } else {
+            return;
         };
-        if let Some(position) = position {
-            self.items_changed(position as u32, 1, 0);
-        }
+
+        self.items_changed(position as u32, 1, 0);
+    }
+
+    pub fn get_by_id(&self, flow_id: &FlowId) -> Option<IdentityVerification> {
+        let priv_ = imp::VerificationList::from_instance(self);
+
+        priv_.list.borrow().get(flow_id).cloned()
     }
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]