Skip to main content

quorum_public/utility/
auth_common.rs

1//! This file contains common authentication utilities and functions used across the Quorum server.
2//! Specifically functions and code that is/might be commonly used throughout the server, where it doesn't fit in `/db/queries` or `/route`
3
4use crate::models::user::EmailBackupCode;
5use argon2::{
6    Algorithm, Argon2, Params, Version,
7    password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString},
8};
9use email_address::*;
10use rand::RngExt;
11use rand_core::OsRng;
12
13/// Generates the plain text backup codes.
14///
15/// Uses a `CHARSET` list excluding confusing characters like `0`, `O`, `1`, `l`, etc. to generate a list of backup codes.
16///
17/// # Arguments
18/// * `length` - The length of each backup code.
19///
20/// # Returns
21/// A `Vec<String>` containing the generated backup codes.
22///
23/// # Example
24/// ```
25/// let backup_codes = generate_backup_codes(8);
26/// ```
27pub fn generate_backup_code(length: usize) -> String {
28    const CHARSET: &[u8] = b"ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz23456789";
29    let mut rng = rand::rng();
30
31    (0..length)
32        .map(|_| {
33            let idx = rng.random_range(0..CHARSET.len());
34            CHARSET[idx] as char
35        })
36        .collect()
37}
38
39/// Main entry function to generate backup codes, salts and hashing.
40///
41/// Generates a total of 10 backup codes with the appropriate salts and hashes for secure storage in the database.
42/// Each backup code generated is 24 characters long.
43///
44/// # Returns
45/// A `Vec<EmailBackupCode>` containing the generated backup codes, salts, and hashes
46///
47/// # Example
48/// ```
49/// let backup_codes = generate_backup_codes();
50/// ```
51pub fn generate_backup_codes() -> Vec<EmailBackupCode> {
52    let mut backup_code_array = Vec::with_capacity(10);
53    for _ in 0..10 {
54        let code = generate_backup_code(24);
55        let hashed_code = hash(&code).expect("Failed to hash backup code");
56        backup_code_array.push(EmailBackupCode {
57            plain: Some(code),
58            hash: hashed_code,
59        });
60    }
61    backup_code_array
62}
63
64pub fn get_argon2() -> Argon2<'static> {
65    let params = Params::new(
66        262_144, // 256 MiB in KiB
67        3,       // time cost
68        2,       // parallelism
69        Some(32),
70    )
71    .expect("valid Argon2 params");
72
73    Argon2::new(Algorithm::Argon2id, Version::V0x13, params)
74}
75
76pub fn hash(plaintext: &str) -> Result<String, String> {
77    let argon2 = get_argon2();
78    let salt = SaltString::generate(&mut OsRng);
79    argon2
80        .hash_password(plaintext.as_bytes(), &salt)
81        .map(|phc| phc.to_string())
82        .map_err(|e| format!("Failed to hash: {}", e))
83}
84
85pub fn verify(plaintext: &str, stored_hash: &str) -> Result<bool, String> {
86    let argon2 = get_argon2();
87    let parsed_hash =
88        PasswordHash::new(stored_hash).map_err(|e| format!("Failed to parse hash: {}", e))?;
89    argon2
90        .verify_password(plaintext.as_bytes(), &parsed_hash)
91        .map(|_| true)
92        .map_err(|e| format!("Verification failed: {}", e))
93}
94
95pub fn check_email_address(email: &str) -> bool {
96    EmailAddress::is_valid(email)
97}