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
//! Passports are the identification card for a user. Traditionally known as `Account`.
use anyhow::anyhow;
use argon2::{
password_hash::{
rand_core::OsRng,
Encoding,
PasswordHash,
PasswordHasher,
PasswordVerifier,
SaltString,
},
Argon2,
};
use chrono::{
DateTime,
TimeDelta,
Utc,
};
pub use passport_type::PassportType;
use rocket::serde::{
Deserialize,
Serialize,
};
mod passport_type;
/// Defines a passport of a user.
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(crate = "rocket::serde")]
pub struct Passport {
/// The unique id of the passport. For example username, email or some string of your choice.
pub id: String,
/// Password to login to the service. Resides encoded in memory.
password: String,
/// A list of scopes that the user can access.
services: Vec<String>,
/// Type of this passport.
pub account_type: PassportType,
/// Wether the passport is disabled.
pub disabled: bool,
/// Whether the passport has been confirmed. This is useful in combination
/// with for example E-Mail verficiation.
pub confirmed: bool,
/// Determines when this passport expires.
pub expires_at: DateTime<Utc>,
}
impl Passport {
/// Creates a new passport with [Passport::disabled] and [Passport::confirmed] set to `false`.
pub fn new(
id: &str,
password: &str,
services: &[&str],
account_type: PassportType,
) -> anyhow::Result<Self> {
Ok(Self {
id: id.to_string(),
password: Self::hash_password(password)?,
services: services
.iter()
.map(|s| s.to_string())
.collect::<Vec<String>>(),
account_type,
disabled: false, // always activate
confirmed: false, // always require user to confirm it
expires_at: chrono::Utc::now()
+ TimeDelta::try_weeks(104).ok_or(anyhow!(
"Internal server error. Could not create TimeDelta with \
two years."
))?,
})
}
/// Returns the services this passport is valid for.
pub fn services(&self) -> &[String] {
&self.services
}
/// Saves the ```new_password``` to the struct after verifying the ```old_password```.
/// Does NOT automatically call the ```update``` function to update the database.
pub fn change_password(
&mut self,
old_password: &str,
new_password: &str,
) -> anyhow::Result<()> {
if self.verify_password(old_password)? {
self.password = Self::hash_password(new_password)?;
Ok(())
} else {
Err(anyhow!("Passwords do not match."))
}
}
/// Checks if the given password is correct.
pub fn verify_password(&self, password: &str) -> anyhow::Result<bool> {
let hash = PasswordHash::parse(&self.password, Encoding::B64)
.map_err(|e| anyhow!("{e}"))?;
Ok(Argon2::default()
.verify_password(password.as_bytes(), &hash)
.is_ok())
}
/// Hashes the password using `[argon2]`.
fn hash_password(password: &str) -> anyhow::Result<String> {
let salt = SaltString::generate(&mut OsRng);
let argon2 = Argon2::default();
Ok(argon2
.hash_password(password.as_bytes(), &salt)
.map_err(|e| anyhow!("{e}"))?
.to_string())
}
}