1use crate::base::LKCError;
2use chrono::{prelude::*, Duration, NaiveDateTime};
3use hex;
4use lazy_static::lazy_static;
5use rand::prelude::*;
6use regex::Regex;
7use std::fmt;
8
9lazy_static! {
10 #[doc(hidden)]
11 pub static ref RANDOMID_PATTERN: regex::Regex =
12 Regex::new(r"^[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}$")
13 .unwrap();
14
15 #[doc(hidden)]
16 pub static ref USERID_PATTERN: regex::Regex =
17 Regex::new(r"^([\p{L}\p{M}\p{N}\-_]|\.[^.])+$")
18 .unwrap();
19
20 #[doc(hidden)]
21 pub static ref CONTROL_CHARS_PATTERN: regex::Regex =
22 Regex::new(r#"\p{C}"#)
23 .unwrap();
24
25 #[doc(hidden)]
26 pub static ref DOMAIN_PATTERN: regex::Regex =
27 Regex::new(r"^([a-zA-Z0-9\-]+)(\.[a-zA-Z0-9\-]+)*$")
28 .unwrap();
29
30}
31
32#[derive(Debug, PartialEq, PartialOrd, Clone, Copy)]
34#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
35pub enum IDType {
36 WorkspaceID,
37 UserID,
38}
39
40#[derive(Debug, PartialEq, PartialOrd, Clone)]
45#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
46pub struct RandomID {
47 data: String,
48}
49
50impl RandomID {
51 pub fn generate() -> RandomID {
53 let mut rdata: [u8; 16] = [0; 16];
54 rand::thread_rng().fill_bytes(&mut rdata[..]);
55 let out = RandomID {
56 data: format!(
57 "{}-{}-{}-{}-{}",
58 hex::encode(&rdata[0..4]),
59 hex::encode(&rdata[4..6]),
60 hex::encode(&rdata[6..8]),
61 hex::encode(&rdata[8..10]),
62 hex::encode(&rdata[10..])
63 ),
64 };
65
66 out
67 }
68
69 pub fn from(data: &str) -> Option<RandomID> {
71 if !RANDOMID_PATTERN.is_match(data) {
72 return None;
73 }
74
75 let mut out = RandomID {
76 data: String::from("00000000-0000-0000-0000-000000000000"),
77 };
78 out.data = data.to_lowercase();
79
80 Some(out)
81 }
82
83 pub fn from_userid(uid: &UserID) -> Option<RandomID> {
86 match uid.get_type() {
87 IDType::UserID => None,
88 IDType::WorkspaceID => Some(RandomID {
89 data: uid.to_string(),
90 }),
91 }
92 }
93
94 pub fn as_string(&self) -> &str {
96 &self.data
97 }
98}
99
100impl fmt::Display for RandomID {
101 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
102 write!(f, "{}", self.data)
103 }
104}
105
106#[derive(Debug, PartialEq, PartialOrd, Clone)]
110#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
111pub struct UserID {
112 data: String,
113 idtype: IDType,
114}
115
116impl UserID {
117 pub fn from(data: &str) -> Option<UserID> {
120 if data.len() > 64 || data.len() == 0 {
121 return None;
122 }
123
124 if !USERID_PATTERN.is_match(data) {
125 return None;
126 }
127
128 let mut out = UserID {
129 data: String::from(data),
130 idtype: IDType::UserID,
131 };
132 out.data = data.to_lowercase();
133
134 out.idtype = if RANDOMID_PATTERN.is_match(&out.data) {
135 IDType::WorkspaceID
136 } else {
137 IDType::UserID
138 };
139
140 Some(out)
141 }
142
143 pub fn from_wid(wid: &RandomID) -> UserID {
145 UserID {
146 data: String::from(wid.as_string()),
147 idtype: IDType::WorkspaceID,
148 }
149 }
150
151 pub fn as_string(&self) -> &str {
153 &self.data
154 }
155
156 pub fn get_type(&self) -> IDType {
158 self.idtype
159 }
160}
161
162impl fmt::Display for UserID {
163 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
164 write!(f, "{}", self.data)
165 }
166}
167
168#[derive(Debug, PartialEq, PartialOrd, Clone)]
170#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
171pub struct Domain {
172 data: String,
173}
174
175impl Domain {
176 pub fn from(data: &str) -> Option<Domain> {
180 if !DOMAIN_PATTERN.is_match(data) {
181 return None;
182 }
183
184 let mut out = Domain {
185 data: String::from(data),
186 };
187 out.data = data.to_lowercase();
188
189 Some(out)
190 }
191
192 pub fn as_string(&self) -> &str {
194 &self.data
195 }
196
197 pub fn parent(&self) -> Option<Domain> {
200 let domparts: Vec<&str> = self.data.split(".").collect();
201 if domparts.len() < 3 {
202 return None;
203 }
204
205 return Some(Domain {
206 data: String::from(&domparts[1..].join(".")),
207 });
208 }
209
210 pub fn pop(&mut self) -> Result<(), LKCError> {
214 let domparts: Vec<&str> = self.data.split(".").collect();
215 if domparts.len() < 3 {
216 return Err(LKCError::ErrOutOfRange);
217 }
218
219 self.data = String::from(&domparts[1..].join("."));
220 Ok(())
221 }
222
223 pub fn push(&mut self, subdom: &str) -> Result<(), LKCError> {
225 let mut domparts: Vec<&str> = self.data.split(".").collect();
226 let subdomparts: Vec<&str> = subdom.split(".").collect();
227
228 for part in subdomparts.iter().rev() {
229 domparts.insert(0, part);
230 }
231
232 self.data = String::from(&domparts.join("."));
233
234 Ok(())
235 }
236}
237
238impl fmt::Display for Domain {
239 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
240 write!(f, "{}", self.data)
241 }
242}
243
244#[derive(Debug, PartialEq, PartialOrd, Clone)]
247#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
248pub struct MAddress {
249 pub uid: UserID,
250 pub domain: Domain,
251 address: String,
252}
253
254impl MAddress {
255 pub fn from(data: &str) -> Option<MAddress> {
258 let parts = data.split("/").collect::<Vec<&str>>();
259
260 if parts.len() != 2 {
261 return None;
262 }
263
264 let out = MAddress {
265 uid: UserID::from(parts[0])?,
266 domain: Domain::from(parts[1])?,
267 address: format!("{}/{}", parts[0], parts[1]),
268 };
269
270 Some(out)
271 }
272
273 pub fn from_waddress(waddr: &WAddress) -> MAddress {
275 let uid = UserID::from_wid(&waddr.wid);
276 MAddress {
277 address: format!("{}/{}", uid, waddr.domain),
278 uid,
279 domain: waddr.domain.clone(),
280 }
281 }
282
283 pub fn from_parts(uid: &UserID, domain: &Domain) -> MAddress {
285 MAddress {
286 uid: uid.clone(),
287 domain: domain.clone(),
288 address: format!("{}/{}", uid, domain),
289 }
290 }
291
292 pub fn as_string(&self) -> String {
294 format!("{}/{}", self.uid, self.domain)
295 }
296
297 pub fn get_uid(&self) -> &UserID {
299 &self.uid
300 }
301
302 pub fn get_domain(&self) -> &Domain {
304 &self.domain
305 }
306}
307
308impl fmt::Display for MAddress {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 write!(f, "{}/{}", self.uid, self.domain)
311 }
312}
313
314#[derive(Debug, PartialEq, PartialOrd, Clone)]
317#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
318pub struct WAddress {
319 wid: RandomID,
320 domain: Domain,
321
322 address: String,
325}
326
327impl WAddress {
328 pub fn from(data: &str) -> Option<WAddress> {
331 let parts = data.split("/").collect::<Vec<&str>>();
332
333 if parts.len() != 2 {
334 return None;
335 }
336
337 let out = WAddress {
338 wid: RandomID::from(parts[0])?,
339 domain: Domain::from(parts[1])?,
340 address: format!("{}/{}", parts[0], parts[1]),
341 };
342
343 Some(out)
344 }
345
346 pub fn from_maddress(maddr: &MAddress) -> Option<WAddress> {
349 match maddr.uid.get_type() {
350 IDType::UserID => None,
351 IDType::WorkspaceID => Some(WAddress {
352 wid: RandomID::from_userid(&maddr.uid).unwrap(),
353 domain: maddr.domain.clone(),
354 address: format!("{}/{}", maddr.uid, maddr.domain),
355 }),
356 }
357 }
358
359 pub fn from_parts(wid: &RandomID, domain: &Domain) -> WAddress {
361 WAddress {
362 wid: wid.clone(),
363 domain: domain.clone(),
364 address: format!("{}/{}", wid, domain),
365 }
366 }
367
368 pub fn as_string(&self) -> String {
370 format!("{}/{}", self.wid, self.domain)
371 }
372
373 pub fn get_wid(&self) -> &RandomID {
375 &self.wid
376 }
377
378 pub fn get_domain(&self) -> &Domain {
380 &self.domain
381 }
382}
383
384impl fmt::Display for WAddress {
385 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
386 write!(f, "{}/{}", self.wid, self.domain)
387 }
388}
389
390#[derive(Debug, PartialEq, PartialOrd, Clone)]
393#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
394pub struct ArgonHash {
395 hash: String,
396 hashtype: String,
397}
398
399impl ArgonHash {
400 pub fn from(password: &str) -> ArgonHash {
402 ArgonHash {
403 hash: eznacl::hash_password(password, &eznacl::HashStrength::Basic),
404 hashtype: String::from("argon2id"),
405 }
406 }
407
408 pub fn from_hashstr(passhash: &str) -> ArgonHash {
410 ArgonHash {
411 hash: String::from(passhash),
412 hashtype: String::from("argon2id"),
413 }
414 }
415
416 pub fn get_hash(&self) -> &str {
418 &self.hash
419 }
420
421 pub fn get_hashtype(&self) -> &str {
423 &self.hashtype
424 }
425}
426
427impl fmt::Display for ArgonHash {
428 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429 write!(f, "{}", self.hash)
430 }
431}
432
433#[derive(Debug, PartialEq, PartialOrd, Clone)]
436#[cfg_attr(feature = "use_serde", derive(serde::Serialize, serde::Deserialize))]
437pub struct Timestamp {
438 datetime: NaiveDateTime,
439 data: String,
440}
441
442impl Timestamp {
443 pub fn new() -> Timestamp {
445 let utc: DateTime<Utc> = Utc::now();
446 let formatted = utc.format("%Y%m%dT%H%M%SZ");
447
448 Timestamp::from_str(formatted.to_string().as_str()).unwrap()
449 }
450
451 pub fn from_str(data: &str) -> Option<Timestamp> {
453 let datetime = match chrono::NaiveDateTime::parse_from_str(data, "%Y%m%dT%H%M%SZ") {
454 Ok(v) => v,
455 Err(_) => return None,
456 };
457
458 Some(Timestamp {
459 datetime,
460 data: String::from(data.to_uppercase()),
461 })
462 }
463
464 pub fn from_datestr(date: &str) -> Option<Self> {
467 let datetime = format!("{}T000000Z", date);
468 Timestamp::from_str(&datetime)
469 }
470
471 pub fn with_offset(&self, days: i64) -> Option<Timestamp> {
473 let offset_date = self.datetime.checked_add_signed(Duration::days(days))?;
474 Some(Timestamp {
475 datetime: offset_date,
476 data: offset_date.format("%Y%m%dT%H%M%SZ").to_string(),
477 })
478 }
479
480 pub fn as_datestr(&self) -> &str {
482 &self.data[0..8]
483 }
484}
485
486impl fmt::Display for Timestamp {
487 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
488 write!(f, "{}", self.data)
489 }
490}
491
492#[cfg(test)]
493mod tests {
494 use crate::*;
495
496 #[test]
497 fn test_randomid() {
498 let testid = RandomID::generate();
499
500 let strid = RandomID::from(testid.as_string());
501 assert_ne!(strid, None);
502 }
503
504 #[test]
505 fn test_userid() {
506 assert_ne!(UserID::from("valid_e-mail.123"), None);
507
508 match UserID::from("11111111-1111-1111-1111-111111111111") {
509 Some(v) => {
510 assert!(v.get_type() == IDType::WorkspaceID)
511 }
512 None => {
513 panic!("test_userid failed workspace ID assignment")
514 }
515 }
516
517 match UserID::from("Valid.but.needs_case-squashed") {
518 Some(v) => {
519 assert_eq!(v.as_string(), "valid.but.needs_case-squashed")
520 }
521 None => {
522 panic!("test_userid failed case-squashing check")
523 }
524 }
525
526 assert_eq!(UserID::from("invalid..number1"), None);
527 assert_eq!(UserID::from("invalid#2"), None);
528 }
529
530 #[test]
531 fn test_domain() {
532 assert!(Domain::from("foo-bar").is_some());
535 assert!(Domain::from("foo-bar.baz.com").is_some());
536
537 match Domain::from("FOO.bar.com") {
538 Some(v) => {
539 assert_eq!(v.as_string(), "foo.bar.com")
540 }
541 None => {
542 panic!("test_domain failed case-squashing check")
543 }
544 }
545
546 assert!(Domain::from("a bad-id.com").is_none());
547 assert!(Domain::from("also_bad.org").is_none());
548
549 assert!(Domain::from("foo-bar.baz.com").unwrap().parent().is_some());
552 assert!(Domain::from("baz.com").unwrap().parent().is_none());
553 assert_eq!(
554 Domain::from("foo-bar.baz.com")
555 .unwrap()
556 .parent()
557 .unwrap()
558 .to_string(),
559 "baz.com"
560 );
561
562 let mut d = Domain::from("baz.com").unwrap();
565 assert!(d.push("foo.bar").is_ok());
566 assert_eq!(d.to_string(), "foo.bar.baz.com");
567 assert!(d.pop().is_ok());
568 assert_eq!(d.to_string(), "bar.baz.com");
569 assert!(d.pop().is_ok());
570 assert_eq!(d.to_string(), "baz.com");
571 assert!(d.pop().is_err());
572 }
573
574 #[test]
575 fn test_maddress() {
576 assert_ne!(MAddress::from("cats4life/example.com"), None);
577 assert_ne!(
578 MAddress::from("5a56260b-aa5c-4013-9217-a78f094432c3/example.com"),
579 None
580 );
581
582 let waddr = WAddress::from("5a56260b-aa5c-4013-9217-a78f094432c3/example.com").unwrap();
583 assert_eq!(
584 MAddress::from_waddress(&waddr).to_string(),
585 "5a56260b-aa5c-4013-9217-a78f094432c3/example.com"
586 );
587
588 assert_eq!(MAddress::from("has spaces/example.com"), None);
589 assert_eq!(MAddress::from(r#"has_a_"/example.com"#), None);
590 assert_eq!(MAddress::from("\\not_allowed/example.com"), None);
591 assert_eq!(MAddress::from("/example.com"), None);
592 assert_eq!(
593 MAddress::from("5a56260b-aa5c-4013-9217-a78f094432c3/example.com/example.com"),
594 None
595 );
596 assert_eq!(MAddress::from("5a56260b-aa5c-4013-9217-a78f094432c3"), None);
597 }
598
599 #[test]
600 fn test_waddress() {
601 assert_ne!(
602 WAddress::from("5a56260b-aa5c-4013-9217-a78f094432c3/example.com"),
603 None
604 );
605 assert_eq!(WAddress::from("cats4life/example.com"), None);
606 assert_eq!(
607 WAddress::from_maddress(&MAddress::from("cats4life/example.com").unwrap()),
608 None
609 );
610 assert!(WAddress::from_maddress(
611 &MAddress::from("5a56260b-aa5c-4013-9217-a78f094432c3/example.com").unwrap()
612 )
613 .is_some());
614 }
615
616 #[test]
617 fn test_timestamp() {
618 assert_eq!(Timestamp::from_str("foobar"), None);
619
620 let ts = Timestamp::from_str("20220501T131011Z").unwrap();
621 assert_eq!(&ts.to_string(), "20220501T131011Z");
622 assert_eq!(ts.as_datestr(), "20220501");
623 assert_eq!(&ts.with_offset(1).unwrap().to_string(), "20220502T131011Z");
624
625 let ds = Timestamp::from_datestr("20220502").unwrap();
626 assert_eq!(ds.as_datestr(), "20220502");
627 assert_eq!(&ds.to_string(), "20220502T000000Z");
628 }
629}