vapour_protocol/auth/
qr.rs1use 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}