Skip to main content

vapour_protocol/auth/
qr.rs

1use std::time::Duration;
2
3use crate::{
4    connection::Connection,
5    error::{Error, Result},
6    protobuf::{
7        CAuthenticationBeginAuthSessionViaQrRequest, CAuthenticationBeginAuthSessionViaQrResponse,
8        CAuthenticationDeviceDetails, CAuthenticationPollAuthSessionStatusRequest,
9        CAuthenticationPollAuthSessionStatusResponse,
10    },
11    service_method::{ServiceMethod, call},
12};
13
14const BEGIN_QR_METHOD: &str = "Authentication.BeginAuthSessionViaQR#1";
15const POLL_METHOD: &str = "Authentication.PollAuthSessionStatus#1";
16
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct QrChallenge {
19    pub client_id: u64,
20    pub request_id: Vec<u8>,
21    pub challenge_url: String,
22    pub interval: Duration,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct CompletedAuth {
27    pub refresh_token: String,
28    pub account_name: String,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum PollState {
33    Pending { challenge_changed: bool },
34    Complete(CompletedAuth),
35}
36
37pub async fn begin(
38    connection: &Connection,
39    device_friendly_name: &str,
40    platform_type: i32,
41    device_details: CAuthenticationDeviceDetails,
42    website_id: &str,
43) -> Result<QrChallenge> {
44    let response: CAuthenticationBeginAuthSessionViaQrResponse = call(
45        connection,
46        &ServiceMethod::new(BEGIN_QR_METHOD),
47        &CAuthenticationBeginAuthSessionViaQrRequest {
48            device_friendly_name: Some(device_friendly_name.to_owned()),
49            platform_type: Some(platform_type),
50            device_details: Some(device_details),
51            website_id: Some(website_id.to_owned()),
52        },
53    )
54    .await?;
55
56    Ok(QrChallenge {
57        client_id: response.client_id.ok_or(Error::MissingField(
58            "CAuthenticationBeginAuthSessionViaQrResponse.client_id",
59        ))?,
60        request_id: response.request_id.ok_or(Error::MissingField(
61            "CAuthenticationBeginAuthSessionViaQrResponse.request_id",
62        ))?,
63        challenge_url: response.challenge_url.ok_or(Error::MissingField(
64            "CAuthenticationBeginAuthSessionViaQrResponse.challenge_url",
65        ))?,
66        interval: Duration::from_secs_f32(response.interval.unwrap_or(5.0).max(1.0)),
67    })
68}
69
70pub async fn poll(connection: &Connection, challenge: &mut QrChallenge) -> Result<PollState> {
71    let response: CAuthenticationPollAuthSessionStatusResponse = call(
72        connection,
73        &ServiceMethod::new(POLL_METHOD),
74        &CAuthenticationPollAuthSessionStatusRequest {
75            client_id: Some(challenge.client_id),
76            request_id: Some(challenge.request_id.clone()),
77            token_to_revoke: None,
78        },
79    )
80    .await?;
81
82    if let Some(refresh_token) = response.refresh_token {
83        return Ok(PollState::Complete(CompletedAuth {
84            refresh_token,
85            account_name: response.account_name.ok_or(Error::MissingField(
86                "CAuthenticationPollAuthSessionStatusResponse.account_name",
87            ))?,
88        }));
89    }
90
91    let mut challenge_changed = false;
92    if let Some(new_client_id) = response.new_client_id {
93        challenge.client_id = new_client_id;
94    }
95    if let Some(new_challenge_url) = response.new_challenge_url
96        && new_challenge_url != challenge.challenge_url
97    {
98        challenge.challenge_url = new_challenge_url;
99        challenge_changed = true;
100    }
101
102    Ok(PollState::Pending { challenge_changed })
103}