1use anyhow::{Context, Result};
5use futures::future::BoxFuture;
6
7use crate::server::{
8 AccessToken, AuditPage, AuditQuery, DevicePoll, DeviceRequest, Invite, Member, Role,
9 SecretInfo, Via, Who,
10};
11
12pub trait AccountStore: Send + Sync {
14 fn tidy(&self) {}
16 fn member(&self, email: &str) -> Result<Option<Who>>;
18 fn members(&self) -> Result<Vec<Member>>;
19 fn set_role(&self, by: &str, email: &str, role: Role) -> Result<()>;
20 fn remove(&self, by: &str, email: &str) -> Result<()>;
22 fn invite(&self, by: &str, email: &str, role: Role, code: bool) -> Result<String>;
24 fn invites(&self) -> Result<Vec<Invite>>;
25 fn revoke_invite(&self, by: &str, email: &str) -> Result<()>;
26 fn sign_in_link(&self, email: &str) -> Result<Option<String>>;
28 fn redeem(&self, token: &str, client: &str) -> Result<(String, Who)>;
30 fn sign_in_code(
32 &self,
33 email: &str,
34 code: &str,
35 client: &str,
36 ) -> Result<(String, Who, Option<String>)>;
37 fn sign_in_verified(&self, email: &str, how: &str, join: bool) -> Result<(String, Who)>;
40 fn admit(&self, email: &str, join: bool) -> Result<Who>;
43 fn reset_code(&self, by: &str, email: &str, keep: Option<&str>) -> Result<String>;
45 fn sign_out(&self, session: &str, everywhere: bool) -> Result<()>;
46 fn create_token(&self, who: &Who, name: &str, expires_in_days: Option<u32>) -> Result<String>;
48 fn tokens(&self, who: &Who) -> Result<Vec<AccessToken>>;
49 fn revoke_token(&self, who: &Who, id: &str) -> Result<()>;
50 fn revoke_own_token(&self, token: &str) -> Result<bool>;
52}
53
54pub trait AuditLog: Send + Sync {
56 fn audit_entries(&self, query: &AuditQuery) -> Result<AuditPage>;
58 fn record(&self, actor: &str, action: &str, detail: &str);
60}
61
62pub trait DeviceSignIn: Send + Sync {
65 fn device_start(&self, client: &str, from: &str) -> Result<(String, String)>;
67 fn device_info(&self, user_code: &str) -> Option<DeviceRequest>;
68 fn device_approve(&self, who: &Who, user_code: &str, approve: bool) -> Result<()>;
69 fn device_poll(&self, device_code: &str) -> Result<DevicePoll>;
70}
71
72pub trait Authenticator: Send + Sync {
74 fn authenticate(
75 &self,
76 session: Option<&str>,
77 bearer: Option<&str>,
78 ) -> Result<Option<(Who, Via)>>;
79}
80
81pub trait SecretStore: Send + Sync {
83 fn set_secret(&self, by: &str, name: &str, value: &str) -> Result<()>;
85 fn secret(&self, name: &str) -> Option<String>;
86 fn secret_info(&self, name: &str) -> Option<SecretInfo>;
87 fn secret_names(&self) -> Vec<String>;
88}
89
90pub trait SettingsStore: Send + Sync {
92 fn repos(&self) -> Vec<crate::config::RepoEntry>;
93 fn set_repos(&self, by: &str, repos: &[crate::config::RepoEntry]) -> Result<()>;
94 fn legacy_config(&self) -> Option<String>;
96 fn drop_legacy_config(&self) -> Result<()>;
97 fn log_skip(&self) -> Option<String>;
99 fn set_log_skip(&self, by: &str, skip: &str) -> Result<()>;
100}
101
102pub trait Mailer: Send + Sync {
104 fn send<'a>(
105 &'a self,
106 to: &'a str,
107 subject: &'a str,
108 body: &'a str,
109 ) -> BoxFuture<'a, Result<()>>;
110}
111
112pub struct Smtp {
114 pub url: Option<String>,
115 pub from: String,
116}
117
118impl Mailer for Smtp {
119 fn send<'a>(
120 &'a self,
121 to: &'a str,
122 subject: &'a str,
123 body: &'a str,
124 ) -> BoxFuture<'a, Result<()>> {
125 Box::pin(async move {
126 use lettre::message::header::ContentType;
127 use lettre::{AsyncSmtpTransport, AsyncTransport, Message, Tokio1Executor};
128 let url = self.url.as_deref().context("no mail server configured")?;
129 let msg = Message::builder()
130 .from(
131 self.from
132 .parse()
133 .context("COMINDLY_SMTP_FROM is not an address")?,
134 )
135 .to(to.parse().context("not an email address")?)
136 .subject(subject)
137 .header(ContentType::TEXT_PLAIN)
138 .body(body.to_string())?;
139 AsyncSmtpTransport::<Tokio1Executor>::from_url(url)
140 .context("COMINDLY_SMTP_URL is not a valid smtp:// or smtps:// URL")?
141 .build()
142 .send(msg)
143 .await
144 .map_err(|e| anyhow::anyhow!("sending mail failed: {e}"))?;
145 Ok(())
146 })
147 }
148}