1#![deny(unsafe_code)]
2#![warn(missing_docs)]
3pub mod config;
11pub mod otp;
12pub mod send;
13
14pub use config::EmailConfig;
15pub use otp::{generate_otp, send_otp, verify_otp};
16pub use send::send_email;
17
18pub const CREATE_OTP_TABLE_SQLITE: &str = r#"
20CREATE TABLE IF NOT EXISTS atrg_otp_codes (
21 id INTEGER PRIMARY KEY AUTOINCREMENT,
22 did TEXT NOT NULL,
23 email TEXT NOT NULL,
24 code TEXT NOT NULL,
25 expires_at INTEGER NOT NULL,
26 used INTEGER NOT NULL DEFAULT 0,
27 created_at INTEGER NOT NULL DEFAULT (unixepoch())
28);
29CREATE INDEX IF NOT EXISTS idx_atrg_otp_did_email ON atrg_otp_codes(did, email);
30"#;
31
32pub const CREATE_OTP_TABLE_POSTGRES: &str = r#"
34CREATE TABLE IF NOT EXISTS atrg_otp_codes (
35 id BIGSERIAL PRIMARY KEY,
36 did TEXT NOT NULL,
37 email TEXT NOT NULL,
38 code TEXT NOT NULL,
39 expires_at BIGINT NOT NULL,
40 used BOOLEAN NOT NULL DEFAULT FALSE,
41 created_at BIGINT NOT NULL DEFAULT EXTRACT(EPOCH FROM NOW())::bigint
42);
43CREATE INDEX IF NOT EXISTS idx_atrg_otp_did_email ON atrg_otp_codes(did, email);
44"#;
45
46pub fn validate_domain(email: &str, allowed_domains: &[String]) -> Result<(), String> {
49 if allowed_domains.is_empty() {
50 return Ok(()); }
52 let domain = email.split('@').nth(1).unwrap_or("").to_lowercase();
53 if allowed_domains.iter().any(|d| d.to_lowercase() == domain) {
54 Ok(())
55 } else {
56 Err(format!(
57 "Email domain '{}' is not allowed. Allowed: {}",
58 domain,
59 allowed_domains.join(", ")
60 ))
61 }
62}