1use std::convert::TryFrom;
2
3use serde_derive::{Deserialize, Serialize};
4use sha2::Digest;
5use thiserror::Error;
6
7use super::default_none;
8use crate::db::models as db_models;
9
10#[derive(Debug, Error)]
11pub enum UserError {
12 #[error("error parsing date: {0}")]
13 Chrono(#[from] chrono::ParseError),
14 #[error("error parsing uuid: {0}")]
15 Uuid(#[from] uuid::Error),
16 #[error("error decoding hex data: {0}")]
17 Hex(#[from] hex::FromHexError),
18 #[error("error decoding UTF-8 data: {0}")]
19 Utf8(#[from] std::string::FromUtf8Error),
20}
21
22#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase", deny_unknown_fields)]
24pub struct User {
25 pub name: String,
26 #[serde(
27 serialize_with = "hex::serialize",
28 deserialize_with = "hex::deserialize"
29 )]
30 pub password: Vec<u8>,
31 #[serde(
32 serialize_with = "hex::serialize",
33 deserialize_with = "hex::deserialize"
34 )]
35 pub token: Vec<u8>,
36 pub salt: String,
37 #[serde(default = "default_none")]
38 pub comment: Option<String>,
39 #[serde(default = "default_none")]
40 pub id: Option<String>,
41 #[serde(default = "chrono::Utc::now")]
42 pub created_at: chrono::DateTime<chrono::Utc>,
43 #[serde(default = "chrono::Utc::now")]
44 pub last_use: chrono::DateTime<chrono::Utc>,
45}
46
47impl User {
48 pub fn hyperion() -> Self {
49 let name = "Hyperion".to_owned();
50 let salt = Self::generate_salt();
51 let token = Self::generate_token();
52 let password = Self::hash_password("hyperion", salt.as_bytes());
53 let created_at = chrono::Utc::now();
54 let last_use = created_at;
55
56 Self {
57 name,
58 password,
59 token,
60 salt,
61 comment: None,
62 id: None,
63 created_at,
64 last_use,
65 }
66 }
67
68 pub fn generate_token() -> Vec<u8> {
69 let mut hasher = sha2::Sha512::default();
70 hasher.update(uuid::Uuid::new_v4().as_bytes());
71 hasher.finalize().to_vec()
72 }
73
74 pub fn generate_salt() -> String {
75 hex::encode(Self::generate_token())
76 }
77
78 pub fn hash_password(password: &str, salt: &[u8]) -> Vec<u8> {
79 let mut hasher = sha2::Sha512::default();
80 hasher.update(password.as_bytes());
81 hasher.update(salt);
82 hasher.finalize().to_vec()
83 }
84}
85
86impl TryFrom<db_models::DbUser> for User {
87 type Error = UserError;
88
89 fn try_from(db: db_models::DbUser) -> Result<Self, Self::Error> {
90 Ok(Self {
91 name: db.user,
92 password: hex::decode(db.password)?,
93 token: hex::decode(db.token)?,
94 salt: String::from_utf8(db.salt)?,
95 comment: db.comment,
96 id: db.id,
97 created_at: chrono::DateTime::parse_from_rfc3339(&db.created_at)?
98 .with_timezone(&chrono::Utc),
99 last_use: chrono::DateTime::parse_from_rfc3339(&db.last_use)?
100 .with_timezone(&chrono::Utc),
101 })
102 }
103}