Files
IdP/tauri_app/src-tauri/src/lib.rs
2026-06-26 15:52:39 +03:00

142 lines
4.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use serde::{Deserialize, Serialize};
use tauri::Manager;
use url::Url;
const MAX_LABEL_LEN: usize = 80;
const MAX_ISSUER_LEN: usize = 80;
const MIN_TOTP_SECRET_LEN: usize = 16;
const MAX_TOTP_SECRET_LEN: usize = 256;
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct ValidationResult {
ok: bool,
value: String,
}
#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct TotpProfileInput {
issuer: String,
label: String,
secret: String,
digits: Option<u8>,
period: Option<u16>,
}
#[tauri::command]
fn validate_qr_session_payload(payload: String) -> Result<ValidationResult, String> {
let trimmed = payload.trim();
if trimmed.is_empty() {
return Err("QR-код пустой".into());
}
let session_id = match Url::parse(trimmed) {
Ok(url) => url
.query_pairs()
.find(|(key, _)| key == "sessionId" || key == "session_id")
.map(|(_, value)| value.to_string())
.or_else(|| {
url.path_segments()
.and_then(|segments| segments.last().map(ToString::to_string))
})
.unwrap_or_default(),
Err(_) => trimmed.to_string(),
};
validate_uuid_like(&session_id)?;
Ok(ValidationResult {
ok: true,
value: session_id,
})
}
#[tauri::command]
fn validate_api_base_url(url: String) -> Result<ValidationResult, String> {
let parsed = Url::parse(url.trim()).map_err(|_| "Некорректный URL API".to_string())?;
match parsed.scheme() {
"http" | "https" => Ok(ValidationResult {
ok: true,
value: parsed.as_str().trim_end_matches('/').to_string(),
}),
_ => Err("URL API должен начинаться с http:// или https://".into()),
}
}
#[tauri::command]
fn validate_totp_profile_input(input: TotpProfileInput) -> Result<ValidationResult, String> {
let issuer = input.issuer.trim();
let label = input.label.trim();
let secret = normalize_base32_secret(&input.secret);
let digits = input.digits.unwrap_or(6);
let period = input.period.unwrap_or(30);
if issuer.is_empty() || issuer.len() > MAX_ISSUER_LEN {
return Err("Название сервиса должно содержать от 1 до 80 символов".into());
}
if label.is_empty() || label.len() > MAX_LABEL_LEN {
return Err("Название аккаунта должно содержать от 1 до 80 символов".into());
}
if secret.len() < MIN_TOTP_SECRET_LEN || secret.len() > MAX_TOTP_SECRET_LEN {
return Err("TOTP secret должен содержать от 16 до 256 символов base32".into());
}
if !secret.chars().all(|ch| matches!(ch, 'A'..='Z' | '2'..='7' | '=')) {
return Err("TOTP secret должен быть в формате base32".into());
}
if digits != 6 && digits != 8 {
return Err("TOTP поддерживает только 6 или 8 цифр".into());
}
if !(15..=120).contains(&period) {
return Err("Период TOTP должен быть от 15 до 120 секунд".into());
}
Ok(ValidationResult {
ok: true,
value: secret,
})
}
fn validate_uuid_like(value: &str) -> Result<(), String> {
let normalized = value.trim();
if normalized.len() != 36 {
return Err("QR-код не содержит корректный UUID сессии".into());
}
for (index, ch) in normalized.chars().enumerate() {
let dash = matches!(index, 8 | 13 | 18 | 23);
if dash && ch != '-' {
return Err("QR-код не содержит корректный UUID сессии".into());
}
if !dash && !ch.is_ascii_hexdigit() {
return Err("QR-код не содержит корректный UUID сессии".into());
}
}
Ok(())
}
fn normalize_base32_secret(secret: &str) -> String {
secret
.trim()
.chars()
.filter(|ch| !ch.is_whitespace() && *ch != '-')
.map(|ch| ch.to_ascii_uppercase())
.collect()
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_stronghold::Builder::new(|password| password.as_bytes().to_vec()).build())
.invoke_handler(tauri::generate_handler![
validate_qr_session_payload,
validate_api_base_url,
validate_totp_profile_input
])
.setup(|app| {
let _ = app.path().app_data_dir().map(std::fs::create_dir_all);
Ok(())
})
.run(tauri::generate_context!())
.expect("ошибка запуска Lendry ID Super App");
}