cosmodrome/server/
passport.rs1use anyhow::anyhow;
3use argon2::{
4 password_hash::{
5 rand_core::OsRng,
6 Encoding,
7 PasswordHash,
8 PasswordHasher,
9 PasswordVerifier,
10 SaltString,
11 },
12 Argon2,
13};
14use chrono::{
15 DateTime,
16 TimeDelta,
17 Utc,
18};
19pub use passport_type::PassportType;
20use rocket::serde::{
21 Deserialize,
22 Serialize,
23};
24
25mod passport_type;
26
27#[derive(Serialize, Deserialize, Clone, Debug)]
29#[serde(crate = "rocket::serde")]
30pub struct Passport {
31 pub id: String,
33 password: String,
35 services: Vec<String>,
37 pub account_type: PassportType,
39 pub disabled: bool,
41 pub confirmed: bool,
44 pub expires_at: DateTime<Utc>,
46}
47
48impl Passport {
49 pub fn new(
51 id: &str,
52 password: &str,
53 services: &[&str],
54 account_type: PassportType,
55 ) -> anyhow::Result<Self> {
56 Ok(Self {
57 id: id.to_string(),
58 password: Self::hash_password(password)?,
59 services: services
60 .iter()
61 .map(|s| s.to_string())
62 .collect::<Vec<String>>(),
63 account_type,
64 disabled: false, confirmed: false, expires_at: chrono::Utc::now()
67 + TimeDelta::try_weeks(104).ok_or(anyhow!(
68 "Internal server error. Could not create TimeDelta with \
69 two years."
70 ))?,
71 })
72 }
73
74 pub fn services(&self) -> &[String] {
76 &self.services
77 }
78
79 pub fn change_password(
82 &mut self,
83 old_password: &str,
84 new_password: &str,
85 ) -> anyhow::Result<()> {
86 if self.verify_password(old_password)? {
87 self.password = Self::hash_password(new_password)?;
88 Ok(())
89 } else {
90 Err(anyhow!("Passwords do not match."))
91 }
92 }
93
94 pub fn verify_password(&self, password: &str) -> anyhow::Result<bool> {
96 let hash = PasswordHash::parse(&self.password, Encoding::B64)
97 .map_err(|e| anyhow!("{e}"))?;
98 Ok(Argon2::default()
99 .verify_password(password.as_bytes(), &hash)
100 .is_ok())
101 }
102
103 fn hash_password(password: &str) -> anyhow::Result<String> {
105 let salt = SaltString::generate(&mut OsRng);
106 let argon2 = Argon2::default();
107 Ok(argon2
108 .hash_password(password.as_bytes(), &salt)
109 .map_err(|e| anyhow!("{e}"))?
110 .to_string())
111 }
112}