1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![warn(clippy::all, clippy::pedantic)]
4#![allow(clippy::missing_errors_doc, clippy::missing_panics_doc)]
5
6use anyhow::bail;
11use regex::Regex;
12
13pub static ACCOUNT_REGEX: std::sync::LazyLock<Regex> = std::sync::LazyLock::new(|| {
18 Regex::new(r"^[a-z][a-z0-9_]{0,13}[a-z0-9]$").expect("failed to create regex")
19});
20
21pub static SUBDOMAIN_REGEX: std::sync::LazyLock<Regex> = std::sync::LazyLock::new(|| {
30 Regex::new(r"^[a-z][a-z0-9\-]{0,35}[a-z0-9][.]$").expect("failed to create regex")
31});
32
33use argon2::Argon2;
34use opaque_ke::ciphersuite::CipherSuite;
35
36pub use chacha20poly1305::aead::OsRng;
37pub use opaque_ke::ServerSetup;
38
39pub(crate) const ZEROED_KEY: [u8; 32] = [0u8; 32];
40pub const EXP_LEN: usize = 8;
41#[cfg(feature = "core")]
42pub(crate) const SIG_LEN: usize = 64;
43pub(crate) const MAC_LEN: usize = 32;
44
45#[cfg(feature = "core")]
46pub mod keys;
47
48pub mod login;
49pub mod registration;
50pub mod token;
51
52#[cfg(feature = "core")]
53mod core;
54#[cfg(feature = "core")]
55pub use core::Auth;
56
57#[cfg(feature = "core")]
58pub mod recovery;
59
60#[cfg(feature = "client")]
61mod client;
62#[cfg(feature = "client")]
63pub use client::AuthClient;
64
65pub struct DefaultCipherSuite;
66
67impl CipherSuite for DefaultCipherSuite {
68 type OprfCs = opaque_ke::Ristretto255;
69 type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, opaque_sha2::Sha512>;
70 type Ksf = Argon2<'static>;
71}
72
73pub fn validate_account(account: &str) -> anyhow::Result<String> {
74 let lowercase_account = account.to_ascii_lowercase();
75
76 if !ACCOUNT_REGEX.is_match(&lowercase_account) {
77 bail!("invalid account name");
78 }
79
80 Ok(lowercase_account)
81}
82
83pub fn validate_domain(base_domains: &Vec<String>, domain: &str) -> bool {
84 for base_domain in base_domains {
85 if domain.ends_with(base_domain) {
86 let mut domain = domain.to_string();
87 domain.truncate(domain.len() - base_domain.len());
88
89 return SUBDOMAIN_REGEX.is_match(&domain);
90 }
91 }
92
93 false
94}
95
96#[cfg(test)]
97mod test {
98 use super::*;
99
100 #[test]
101 fn validate_account_test() -> anyhow::Result<()> {
102 let account = validate_account("my_account")?;
104 assert_eq!(account, "my_account");
105
106 let account = validate_account("MyAccount")?;
107 assert_eq!(account, "myaccount");
108
109 let account = validate_account("myAccount123")?;
110 assert_eq!(account, "myaccount123");
111
112 let account = validate_account("_my_account");
114 assert!(account.is_err());
115
116 let account = validate_account("my_account_");
117 assert!(account.is_err());
118
119 let account = validate_account("1my_account");
120 assert!(account.is_err());
121
122 let account = validate_account("my-account");
123 assert!(account.is_err());
124
125 let account = validate_account("@#$%1234");
126 assert!(account.is_err());
127
128 Ok(())
129 }
130
131 #[test]
132 fn validate_domain_test() {
133 let app_domains = vec!["example.com".to_string(), "sub.example.com".to_string()];
134
135 assert!(validate_domain(&app_domains, "my.example.com"));
137 assert!(validate_domain(&app_domains, "asdf.example.com"));
138 assert!(validate_domain(&app_domains, "as-df.example.com"));
139
140 assert!(!validate_domain(&app_domains, "asdf..example.com"));
142 assert!(!validate_domain(&app_domains, "asdf-example.com"));
143 assert!(!validate_domain(&app_domains, "asdf-.example.com"));
144 assert!(!validate_domain(&app_domains, "asdf-.example.com"));
145 assert!(!validate_domain(&app_domains, "asdf.sub.example.com"));
146 assert!(!validate_domain(&app_domains, "a-sdf.sub.example.com"));
147 }
148}