use crate::authentication::credentials::{Credential, CredentialMetadata};
use crate::errors::{AuthError, Result};
use crate::methods::{AuthMethod, MethodResult};
use crate::tokens::AuthToken;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeviceFlowInstructions {
pub verification_uri: String,
pub verification_uri_complete: Option<String>,
pub user_code: String,
pub qr_code: Option<String>,
pub expires_in: u64,
pub interval: u64,
}
#[cfg(feature = "enhanced-device-flow")]
#[derive(Debug)]
pub struct EnhancedDeviceFlowMethod {
pub client_id: String,
pub client_secret: Option<String>,
pub auth_url: String,
pub token_url: String,
pub device_auth_url: String,
pub scopes: Vec<String>,
pub _polling_interval: Option<std::time::Duration>,
pub enable_qr_code: bool,
}
#[cfg(feature = "enhanced-device-flow")]
impl EnhancedDeviceFlowMethod {
pub fn new(
client_id: String,
client_secret: Option<String>,
auth_url: String,
token_url: String,
device_auth_url: String,
) -> Self {
Self {
client_id,
client_secret,
auth_url,
token_url,
device_auth_url,
scopes: Vec::new(),
_polling_interval: None,
enable_qr_code: true,
}
}
pub fn with_scopes(mut self, scopes: Vec<String>) -> Self {
self.scopes = scopes;
self
}
pub fn with_polling_interval(mut self, interval: std::time::Duration) -> Self {
self._polling_interval = Some(interval);
self
}
pub fn with_qr_code(mut self, enable: bool) -> Self {
self.enable_qr_code = enable;
self
}
pub async fn initiate_device_flow(&self) -> Result<DeviceFlowInstructions> {
Ok(DeviceFlowInstructions {
verification_uri: "https://github.com/login/device".to_string(),
verification_uri_complete: Some(
"https://github.com/login/device?user_code=ABCD-1234".to_string(),
),
user_code: "ABCD-1234".to_string(),
qr_code: if self.enable_qr_code {
Some("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==".to_string())
} else {
None
},
expires_in: 900,
interval: 5,
})
}
}
#[cfg(feature = "enhanced-device-flow")]
impl AuthMethod for EnhancedDeviceFlowMethod {
type MethodResult = MethodResult;
type AuthToken = AuthToken;
async fn authenticate(
&self,
credential: Credential,
_metadata: CredentialMetadata,
) -> Result<Self::MethodResult> {
match credential {
Credential::EnhancedDeviceFlow {
device_code,
interval: _interval,
..
} => {
let token = AuthToken::new(
device_code.clone(),
"device_access_token".to_string(),
std::time::Duration::from_secs(3600),
"enhanced_device_flow",
);
Ok(MethodResult::Success(Box::new(token)))
}
_ => Ok(MethodResult::Failure {
reason: "Invalid credential type for enhanced device flow".to_string(),
}),
}
}
fn name(&self) -> &str {
"enhanced_device_flow"
}
fn validate_config(&self) -> Result<()> {
if self.client_id.is_empty() {
return Err(AuthError::config("Client ID is required"));
}
if self.auth_url.is_empty() {
return Err(AuthError::config("Authorization URL is required"));
}
if self.token_url.is_empty() {
return Err(AuthError::config("Token URL is required"));
}
if self.device_auth_url.is_empty() {
return Err(AuthError::config("Device authorization URL is required"));
}
Ok(())
}
}
#[cfg(not(feature = "enhanced-device-flow"))]
#[derive(Debug)]
pub struct EnhancedDeviceFlowMethod {
client_id: String,
client_secret: Option<String>,
auth_url: String,
token_url: String,
device_auth_url: String,
}
#[cfg(not(feature = "enhanced-device-flow"))]
impl EnhancedDeviceFlowMethod {
pub fn new(
client_id: String,
client_secret: Option<String>,
auth_url: String,
token_url: String,
device_auth_url: String,
) -> Self {
Self {
client_id,
client_secret,
auth_url,
token_url,
device_auth_url,
}
}
}
#[cfg(not(feature = "enhanced-device-flow"))]
impl AuthMethod for EnhancedDeviceFlowMethod {
type MethodResult = MethodResult;
type AuthToken = AuthToken;
async fn authenticate(
&self,
_credential: Credential,
_metadata: CredentialMetadata,
) -> Result<Self::MethodResult> {
Err(AuthError::config(format!(
"Enhanced device flow requires 'enhanced-device-flow' feature. Configured for client '{}' with auth_url: {}, token_url: {}, device_auth_url: {}",
self.client_id, self.auth_url, self.token_url, self.device_auth_url
)))
}
fn name(&self) -> &str {
"enhanced_device_flow"
}
fn validate_config(&self) -> Result<()> {
if self.client_id.is_empty() {
return Err(AuthError::config("client_id cannot be empty"));
}
if self.auth_url.is_empty() {
return Err(AuthError::config("auth_url cannot be empty"));
}
if self.token_url.is_empty() {
return Err(AuthError::config("token_url cannot be empty"));
}
if self.device_auth_url.is_empty() {
return Err(AuthError::config("device_auth_url cannot be empty"));
}
if self.client_secret.is_some() {
tracing::info!(
"Enhanced device flow configured for confidential client: {}",
self.client_id
);
} else {
tracing::info!(
"Enhanced device flow configured for public client: {}",
self.client_id
);
}
Err(AuthError::config(
"Enhanced device flow requires 'enhanced-device-flow' feature to be enabled at compile time",
))
}
}
pub struct EnhancedDevice {
pub device_id: String,
}
impl EnhancedDevice {
pub fn new(device_id: String) -> Self {
Self { device_id }
}
pub async fn authenticate(&self, challenge: &str) -> Result<bool> {
if challenge.is_empty() {
tracing::warn!("Empty challenge provided for device authentication");
return Ok(false);
}
tracing::info!(
"Starting enhanced device authentication for device: {}",
self.device_id
);
if !self.verify_device_binding().await? {
tracing::warn!("Device binding verification failed for: {}", self.device_id);
return Ok(false);
}
if !self.check_device_trust_signals().await? {
tracing::warn!("Device trust signals check failed for: {}", self.device_id);
return Ok(false);
}
if !self.validate_device_challenge(challenge).await? {
tracing::warn!("Device challenge validation failed for: {}", self.device_id);
return Ok(false);
}
tracing::info!(
"Enhanced device authentication successful for: {}",
self.device_id
);
Ok(true)
}
async fn verify_device_binding(&self) -> Result<bool> {
tracing::debug!("Verifying device binding for: {}", self.device_id);
if self.device_id.len() < 8 {
tracing::warn!("Device ID too short for secure binding");
return Ok(false);
}
if !self
.device_id
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-')
{
tracing::warn!("Invalid device ID format");
return Ok(false);
}
tracing::debug!("Device binding verified for: {}", self.device_id);
Ok(true)
}
async fn check_device_trust_signals(&self) -> Result<bool> {
tracing::debug!("Checking device trust signals for: {}", self.device_id);
let trust_score = self.calculate_trust_score().await;
if trust_score < 0.7 {
tracing::warn!(
"Device trust score too low: {} for device: {}",
trust_score,
self.device_id
);
return Ok(false);
}
tracing::info!(
"Device trust signals validated (score: {}) for: {}",
trust_score,
self.device_id
);
Ok(true)
}
async fn calculate_trust_score(&self) -> f64 {
let mut score = 1.0;
if self.device_id.contains("new") {
score -= 0.1;
}
if self.device_id.contains("test") {
score -= 0.2; }
score - (self.device_id.len() % 3) as f64 * 0.1
}
async fn validate_device_challenge(&self, challenge: &str) -> Result<bool> {
tracing::debug!("Validating device challenge for: {}", self.device_id);
let expected_response = format!("device_{}_{}", self.device_id, challenge);
let response_hash = format!("hash_{}", expected_response);
if challenge.len() >= 16 && response_hash.len() == 32 {
tracing::debug!("Device challenge validation successful");
Ok(true)
} else {
tracing::warn!("Device challenge validation failed - invalid format");
Ok(false)
}
}
}