Skip to main content

rustauth_plugins/device_authorization/routes/
verify.rs

1use http::{Method, StatusCode};
2use rustauth_core::api::{create_auth_endpoint, AuthEndpointOptions};
3use rustauth_core::plugin::PluginEndpoint;
4use serde::{Deserialize, Serialize};
5use url::form_urlencoded;
6
7use crate::device_authorization::errors::{oauth_error_response, OAuthDeviceError};
8use crate::device_authorization::store::DeviceCodeStore;
9
10#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
11pub struct DeviceVerificationResponse {
12    pub user_code: String,
13    pub status: String,
14}
15
16pub fn device_verify() -> PluginEndpoint {
17    create_auth_endpoint(
18        "/device",
19        Method::GET,
20        AuthEndpointOptions::new()
21            .operation_id("deviceVerify")
22            .openapi(super::openapi::device_verify_operation()),
23        |context, request| async move {
24            let Some(user_code) = query_param(request.uri().query(), "user_code") else {
25                return oauth_error_response(
26                    StatusCode::BAD_REQUEST,
27                    OAuthDeviceError::InvalidRequest,
28                    "Invalid user code",
29                );
30            };
31            let adapter = context.require_adapter()?;
32            let store = DeviceCodeStore::new(adapter.as_ref());
33            let clean = super::clean_user_code(&user_code);
34            let Some(record) = store.find_by_user_code(&clean).await? else {
35                return oauth_error_response(
36                    StatusCode::BAD_REQUEST,
37                    OAuthDeviceError::InvalidRequest,
38                    "Invalid user code",
39                );
40            };
41            if record.expires_at < time::OffsetDateTime::now_utc() {
42                return oauth_error_response(
43                    StatusCode::BAD_REQUEST,
44                    OAuthDeviceError::ExpiredToken,
45                    "User code has expired",
46                );
47            }
48            super::json_response(
49                StatusCode::OK,
50                &DeviceVerificationResponse {
51                    user_code,
52                    status: record.status.as_str().to_owned(),
53                },
54            )
55        },
56    )
57}
58
59fn query_param(query: Option<&str>, name: &str) -> Option<String> {
60    form_urlencoded::parse(query?.as_bytes())
61        .find(|(key, _)| key == name)
62        .map(|(_, value)| value.into_owned())
63}