alibi-plugins 0.8.0

Built-in authentication plugins and API metadata for Alibi
Documentation
use super::types::{DeviceCodeRequest, DeviceCodeResponse};
use super::{
    DEVICE_STATUS_PENDING, DeviceAuthorizationPlugin, INVALID_CLIENT_ID, build_verification_uris,
    device_error_response, duration_seconds_floor, is_unique_constraint_error, no_store,
};
use crate::helpers::callback_failure;
use alibi_core::{AuthContext, AuthResponse, AuthResult, CreateDeviceCode};
use chrono::Utc;

impl DeviceAuthorizationPlugin {
    pub(in crate::device_authorization) async fn issue_device_code(
        &self,
        body: DeviceCodeRequest,
        ctx: &AuthContext<impl alibi_core::AuthSchema>,
    ) -> AuthResult<AuthResponse> {
        self.issue_device_code_with_fields(body, serde_json::Map::new(), ctx)
            .await
    }
    pub(super) async fn issue_device_code_with_fields(
        &self,
        mut body: DeviceCodeRequest,
        fields: serde_json::Map<String, serde_json::Value>,
        ctx: &AuthContext<impl alibi_core::AuthSchema>,
    ) -> AuthResult<AuthResponse> {
        if body.client_id.is_empty() {
            return device_error_response(400, "invalid_request", "client_id is required");
        }
        body.user_id = body.user_id.filter(|value| !value.is_empty());
        body.scope = body.scope.filter(|value| !value.is_empty());

        if !self.validate_client_id(&body.client_id).await? {
            return device_error_response(400, "invalid_client", INVALID_CLIENT_ID);
        }

        if let Some(callback) = &self.config.on_device_auth_request {
            callback(body.client_id.clone(), body.scope.clone())
                .await
                .map_err(callback_failure)?;
        }

        let expires_at = Utc::now() + self.config.expires_in;
        let polling_interval = self.config.interval.num_milliseconds();
        for _ in 0..3 {
            let device_code = self.generate_device_code().await?;
            if device_code.chars().count() > 191 {
                return device_error_response(
                    400,
                    "invalid_request",
                    "Generated device code must be at most 191 characters",
                );
            }
            let user_code = self.generate_user_code().await?;
            if user_code.chars().count() > 191 {
                return device_error_response(
                    400,
                    "invalid_request",
                    "Generated user code must be at most 191 characters",
                );
            }
            match ctx
                .database
                .create_device_code_with_fields(
                    CreateDeviceCode {
                        device_code: device_code.clone(),
                        user_code: user_code.clone(),
                        user_id: body.user_id.clone(),
                        expires_at,
                        status: DEVICE_STATUS_PENDING.to_owned(),
                        last_polled_at: None,
                        polling_interval: Some(polling_interval),
                        client_id: Some(body.client_id.clone()),
                        scope: body.scope.clone(),
                    },
                    fields.clone(),
                )
                .await
            {
                Ok(_) => {}
                Err(error) if is_unique_constraint_error(&error) => continue,
                Err(error) => return Err(error),
            }

            let (verification_uri, verification_uri_complete) = build_verification_uris(
                self.config.verification_uri.as_deref(),
                &ctx.config.base_url,
                &user_code,
            )?;

            return Ok(no_store(AuthResponse::json(
                200,
                &DeviceCodeResponse {
                    device_code,
                    user_code,
                    verification_uri,
                    verification_uri_complete,
                    expires_in: duration_seconds_floor(self.config.expires_in),
                    interval: duration_seconds_floor(self.config.interval),
                },
            )?));
        }
        device_error_response(
            500,
            "server_error",
            "Failed to generate a unique device code",
        )
    }
}