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;
41pub(crate) const SIG_LEN: usize = 64;
42pub(crate) const MAC_LEN: usize = 32;
43
44#[cfg(feature = "core")]
45pub mod keys;
46
47pub mod login;
48pub mod registration;
49pub mod token;
50
51#[cfg(feature = "core")]
52mod core;
53#[cfg(feature = "core")]
54pub use core::Auth;
55
56#[cfg(feature = "core")]
57pub mod recovery;
58
59#[cfg(feature = "client")]
60mod client;
61#[cfg(feature = "client")]
62pub use client::AuthClient;
63
64pub struct DefaultCipherSuite;
65
66impl CipherSuite for DefaultCipherSuite {
67 type OprfCs = opaque_ke::Ristretto255;
68 type KeyExchange = opaque_ke::TripleDh<opaque_ke::Ristretto255, sha2::Sha512>;
69 type Ksf = Argon2<'static>;
70}
71
72pub fn validate_account(account: &str) -> anyhow::Result<String> {
73 let lowercase_account = account.to_ascii_lowercase();
74
75 if !ACCOUNT_REGEX.is_match(&lowercase_account) {
76 bail!("invalid account name");
77 }
78
79 Ok(lowercase_account)
80}
81
82pub fn validate_domain(base_domains: &Vec<String>, domain: &str) -> bool {
83 for base_domain in base_domains {
84 if domain.ends_with(base_domain) {
85 let mut domain = domain.to_string();
86 domain.truncate(domain.len() - base_domain.len());
87
88 if SUBDOMAIN_REGEX.is_match(&domain) {
89 return true;
90 }
91 }
92 }
93
94 false
95}
96
97#[cfg(test)]
98mod test {
99 use super::*;
100
101 #[test]
102 fn validate_account_test() -> anyhow::Result<()> {
103 let account = validate_account("my_account")?;
105 assert_eq!(account, "my_account");
106
107 let account = validate_account("MyAccount")?;
108 assert_eq!(account, "myaccount");
109
110 let account = validate_account("myAccount123")?;
111 assert_eq!(account, "myaccount123");
112
113 let account = validate_account("_my_account");
115 assert!(account.is_err());
116
117 let account = validate_account("my_account_");
118 assert!(account.is_err());
119
120 let account = validate_account("1my_account");
121 assert!(account.is_err());
122
123 let account = validate_account("my-account");
124 assert!(account.is_err());
125
126 let account = validate_account("@#$%1234");
127 assert!(account.is_err());
128
129 Ok(())
130 }
131
132 #[test]
133 fn validate_domain_test() {
134 let app_domains = vec!["example.com".to_string(), "sub.example.com".to_string()];
135
136 assert!(validate_domain(&app_domains, "my.example.com"));
138 assert!(validate_domain(&app_domains, "asdf.example.com"));
139 assert!(validate_domain(&app_domains, "asdf.sub.example.com"));
140
141 assert!(validate_domain(&app_domains, "as-df.example.com"));
142 assert!(validate_domain(&app_domains, "a-sdf.sub.example.com"));
143
144 assert!(!validate_domain(&app_domains, "asdf..example.com"));
146 assert!(!validate_domain(&app_domains, "asdf-example.com"));
147 assert!(!validate_domain(&app_domains, "asdf-.example.com"));
148 assert!(!validate_domain(&app_domains, "asdf-.example.com"));
149 }
150}