koprogo_api/application/dto/
two_factor_dto.rs1use crate::domain::entities::TwoFactorSecret;
2use chrono::{DateTime, Utc};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Setup2FAResponseDto {
12 pub secret: String, pub qr_code_data_url: String, pub backup_codes: Vec<String>, pub issuer: String, pub account_name: String, }
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct Enable2FADto {
22 pub totp_code: String, }
24
25#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Enable2FAResponseDto {
28 pub success: bool,
29 pub message: String,
30 pub enabled_at: DateTime<Utc>,
31}
32
33#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct Verify2FADto {
40 pub totp_code: String, }
42
43#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct Verify2FAResponseDto {
46 pub success: bool,
47 pub message: String,
48 pub backup_code_used: bool, pub backup_codes_remaining: Option<usize>, }
51
52#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct Disable2FADto {
59 pub current_password: String, }
61
62#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct RegenerateBackupCodesDto {
65 pub totp_code: String, }
67
68#[derive(Debug, Clone, Serialize, Deserialize)]
70pub struct RegenerateBackupCodesResponseDto {
71 pub backup_codes: Vec<String>, pub regenerated_at: DateTime<Utc>,
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct TwoFactorStatusDto {
78 pub is_enabled: bool,
79 pub verified_at: Option<DateTime<Utc>>,
80 pub last_used_at: Option<DateTime<Utc>>,
81 pub backup_codes_remaining: usize,
82 pub backup_codes_low: bool, pub needs_reverification: bool, }
85
86impl From<TwoFactorSecret> for TwoFactorStatusDto {
87 fn from(secret: TwoFactorSecret) -> Self {
88 Self {
89 is_enabled: secret.is_enabled,
90 verified_at: secret.verified_at,
91 last_used_at: secret.last_used_at,
92 backup_codes_remaining: secret.backup_codes_encrypted.len(),
93 backup_codes_low: secret.backup_codes_low(),
94 needs_reverification: secret.needs_reverification(),
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use uuid::Uuid;
103
104 #[test]
105 fn test_two_factor_status_dto_from_entity() {
106 let mut secret =
107 TwoFactorSecret::new(Uuid::new_v4(), "JBSWY3DPEHPK3PXP".to_string()).unwrap();
108
109 secret.enable().unwrap();
110
111 let dto: TwoFactorStatusDto = secret.into();
112
113 assert!(dto.is_enabled);
114 assert!(dto.verified_at.is_some());
115 assert_eq!(dto.backup_codes_remaining, 10);
116 assert!(!dto.backup_codes_low);
117 assert!(!dto.needs_reverification);
118 }
119
120 #[test]
121 fn test_two_factor_status_dto_backup_codes_low() {
122 let _codes: Vec<String> = vec!["CODE1".to_string(), "CODE2".to_string()]; let mut secret =
124 TwoFactorSecret::new(Uuid::new_v4(), "JBSWY3DPEHPK3PXP".to_string()).unwrap();
125
126 for _ in 0..8 {
128 secret.remove_backup_code(0).unwrap();
129 }
130
131 let dto: TwoFactorStatusDto = secret.into();
132
133 assert_eq!(dto.backup_codes_remaining, 2);
134 assert!(dto.backup_codes_low);
135 }
136}