1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
// SPDX-License-Identifier: MIT
// Project: npwg
// File: src/config.rs
// Author: Volker Schwaberow <volker@schwaberow.de>
// Copyright (c) 2022 Volker Schwaberow

use crate::error::{PasswordGeneratorError, Result};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;

pub const DEFINE: &[(&str, &str)] = &[
    ("symbol1", "#%&?@"),
    ("symbol2", "!#$%&*+-./:=?@~"),
    ("symbol3", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"),
    ("digit", "0123456789"),
    ("lowerletter", "abcdefghijklmnopqrstuvwxyz"),
    ("upperletter", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"),
    ("shell", "!\"$&`'"),
    ("homoglyph1", "71lI|"),
    ("homoglyph2", "2Z"),
    ("homoglyph3", "6G"),
    ("homoglyph4", ":;"),
    ("homoglyph5", "^`'"),
    ("homoglyph6", "!|"),
    ("homoglyph7", "<({[]})>"),
    ("homoglyph8", "~-"),
    ("slashes", "/\\"),
    ("brackets", "[]{}()"),
    ("punctuation", "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"),
    ("all", "#%&?@!#$%&*+-./:=?@~0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~!\"$&`'71lI|2Z6G:;^`'!|<({[]})>~-/\\[]{}()!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"),
    ("allprint", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"),
    ("allprintnoquote", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-./:;<=>?@[\\]^_`{|}~"),
    ("allprintnospace", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"),
    ("allprintnospacequote", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-./:;<=>?@[\\]^_`{|}~"),
    ("allprintnospacequotebracket", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-./:;<=>?@[\\]^_`{|}~[]{}()"),
    ("allprintnospacequotebracketpunctuation", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-./:;<=>?@[\\]^_`{|}~[]{}()!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"),
    ("allprintnospacequotebracketpunctuationslashes", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&()*+,-.:;<=>?@[\\]^_`{|}~[]{}()!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"),
    ("allprintnospacequotebracketpunctuationslashesshell", "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ#$%&()*+,-.:;<=>?@[\\]^_`{|}~[]"),
];

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PasswordGeneratorMode {
    Password,
    Diceware,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PasswordGeneratorConfig {
    pub length: usize,
    pub allowed_chars: Vec<char>,
    pub excluded_chars: HashSet<char>,
    pub included_chars: HashSet<char>,
    pub avoid_repetition: bool,
    pub mode: PasswordGeneratorMode,
    pub diceware_words: usize,
    pub num_passwords: usize,
}

impl Default for PasswordGeneratorConfig {
    fn default() -> Self {
        Self::new()
    }
}

impl PasswordGeneratorConfig {
    pub fn new() -> Self {
        let mut config = Self {
            length: 8,
            allowed_chars: Vec::new(),
            excluded_chars: HashSet::new(),
            included_chars: HashSet::new(),
            num_passwords: 1,
            avoid_repetition: false,
            mode: PasswordGeneratorMode::Password,
            diceware_words: 6,
        };
        config.set_allowed_chars("allprint");
        config
    }

    pub fn set_allowed_chars(&mut self, charset_name: &str) {
        if let Some((_, chars)) = DEFINE.iter().find(|(name, _)| *name == charset_name) {
            self.allowed_chars = chars.chars().collect();
        } else {
            if let Some((_, chars)) = DEFINE.iter().find(|(name, _)| *name == "allprint") {
                self.allowed_chars = chars.chars().collect();
            }
        }
    }

    pub fn add_allowed_chars(&mut self, charset_name: &str) {
        if let Some((_, chars)) = DEFINE.iter().find(|(name, _)| *name == charset_name) {
            self.allowed_chars.extend(chars.chars());
        }
    }

    pub fn clear_allowed_chars(&mut self) {
        self.allowed_chars.clear();
    }

    pub fn set_avoid_repeating(&mut self, avoid: bool) {
        self.avoid_repetition = avoid;
    }

    pub fn validate(&self) -> Result<()> {
        if self.allowed_chars.is_empty() {
            return Err(PasswordGeneratorError::InvalidConfig(
                "No allowed characters specified".to_string(),
            ));
        }
        if self.length == 0 {
            return Err(PasswordGeneratorError::InvalidConfig(
                "Password length must be greater than 0".to_string(),
            ));
        }
        if self.num_passwords == 0 {
            return Err(PasswordGeneratorError::InvalidConfig(
                "Number of passwords must be greater than 0".to_string(),
            ));
        }
        Ok(())
    }
    pub fn set_use_words(&mut self, use_words: bool) {
        self.mode = if use_words {
            PasswordGeneratorMode::Diceware
        } else {
            PasswordGeneratorMode::Password
        };
    }
}