WIP
This commit is contained in:
		
						commit
						663af59488
					
				
					 8 changed files with 3534 additions and 0 deletions
				
			
		
							
								
								
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
										
									
									
										vendored
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,3 @@
 | 
			
		|||
/target
 | 
			
		||||
/.vscode
 | 
			
		||||
/config.toml
 | 
			
		||||
							
								
								
									
										3080
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										3080
									
								
								Cargo.lock
									
										
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							
							
								
								
									
										23
									
								
								Cargo.toml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								Cargo.toml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,23 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "subscribe-list"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
actix-session = { version = "0.9.0", features = ["cookie-session"] }
 | 
			
		||||
actix-web = { version = "4.5.1", features = ["secure-cookies"] }
 | 
			
		||||
clap = { version = "4.5.4", features = ["derive"] }
 | 
			
		||||
clio = { version = "0.3.5", features = ["clap-parse"] }
 | 
			
		||||
color-eyre = "0.6.3"
 | 
			
		||||
eyre = "0.6.12"
 | 
			
		||||
futures = "0.3.30"
 | 
			
		||||
indexmap = { version = "2.2.6", features = ["serde"] }
 | 
			
		||||
listenfd = "1.0.1"
 | 
			
		||||
openidconnect = "3.5.0"
 | 
			
		||||
serde = { version = "1.0.197", features = ["derive"] }
 | 
			
		||||
serde_json = "1.0.115"
 | 
			
		||||
tinytemplate = "1.2.1"
 | 
			
		||||
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
 | 
			
		||||
toml = { version = "0.8.12", features = ["preserve_order"] }
 | 
			
		||||
							
								
								
									
										13
									
								
								config.toml.sample
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								config.toml.sample
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,13 @@
 | 
			
		|||
[oidc.google]
 | 
			
		||||
pretty_name = "Google"
 | 
			
		||||
issuer_url = "https://accounts.google.com"
 | 
			
		||||
client_id = "<TODO>"
 | 
			
		||||
secret = "<TODO>"
 | 
			
		||||
scopes = ["email"]
 | 
			
		||||
 | 
			
		||||
[oidc.debian-salsa]
 | 
			
		||||
pretty_name = "Debian Salsa"
 | 
			
		||||
issuer_url = "https://salsa.debian.org"
 | 
			
		||||
client_id = "<TODO>"
 | 
			
		||||
secret = "<TODO>"
 | 
			
		||||
scopes = ["email"]
 | 
			
		||||
							
								
								
									
										193
									
								
								src/app.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										193
									
								
								src/app.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,193 @@
 | 
			
		|||
use crate::config::{Config, OIDCProvider, OIDCProviderState};
 | 
			
		||||
use actix_session::Session;
 | 
			
		||||
use actix_web::{
 | 
			
		||||
    cookie::Cookie,
 | 
			
		||||
    get,
 | 
			
		||||
    http::{
 | 
			
		||||
        header::{ContentType, LOCATION},
 | 
			
		||||
        StatusCode, Uri,
 | 
			
		||||
    },
 | 
			
		||||
    web::{self, ServiceConfig},
 | 
			
		||||
    HttpResponse, HttpResponseBuilder, Responder,
 | 
			
		||||
};
 | 
			
		||||
use openidconnect::{
 | 
			
		||||
    core::CoreAuthenticationFlow, http::HeaderValue, CsrfToken, EndUserEmail, Nonce,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
use std::{fmt::Write, marker::PhantomData};
 | 
			
		||||
use tinytemplate::TinyTemplate;
 | 
			
		||||
 | 
			
		||||
pub trait DynTemplate {
 | 
			
		||||
    fn name(&self) -> &'static str;
 | 
			
		||||
    fn text(&self) -> &'static str;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub trait Template: Serialize
 | 
			
		||||
where
 | 
			
		||||
    PhantomData<Self>: DynTemplate,
 | 
			
		||||
{
 | 
			
		||||
    const NAME: &'static str;
 | 
			
		||||
    const TEXT: &'static str;
 | 
			
		||||
    fn render(&self) -> tinytemplate::error::Result<String> {
 | 
			
		||||
        with_templates(|templates| templates.render(Self::NAME, &self))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: Template> DynTemplate for PhantomData<T> {
 | 
			
		||||
    fn name(&self) -> &'static str {
 | 
			
		||||
        T::NAME
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn text(&self) -> &'static str {
 | 
			
		||||
        T::TEXT
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
macro_rules! templates {
 | 
			
		||||
    (
 | 
			
		||||
        $(
 | 
			
		||||
            #[text = $text:literal]
 | 
			
		||||
            $(#[$meta:meta])*
 | 
			
		||||
            $vis:vis struct $name:ident $fields:tt
 | 
			
		||||
        )*
 | 
			
		||||
    ) => {
 | 
			
		||||
        $(
 | 
			
		||||
            $(#[$meta])*
 | 
			
		||||
            $vis struct $name $fields
 | 
			
		||||
 | 
			
		||||
            impl Template for $name {
 | 
			
		||||
                const NAME: &'static str = stringify!($name);
 | 
			
		||||
                const TEXT: &'static str = $text;
 | 
			
		||||
            }
 | 
			
		||||
        )*
 | 
			
		||||
        const TEMPLATES: &'static [&'static dyn DynTemplate] = &[$(&PhantomData::<$name>,)*];
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Serialize)]
 | 
			
		||||
pub struct SubscriptionLoggedOutTemplateProviders {
 | 
			
		||||
    pub pretty_name: String,
 | 
			
		||||
    pub url: String,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
templates! {
 | 
			
		||||
    #[text = r#"<head><title>Subscription</title></head>
 | 
			
		||||
<body>
 | 
			
		||||
<ul>
 | 
			
		||||
{{- for provider in providers -}}
 | 
			
		||||
<li><a href="{provider.url}">{provider.pretty_name}</a></li>
 | 
			
		||||
{{- endfor -}}
 | 
			
		||||
</ul>
 | 
			
		||||
</body>"#]
 | 
			
		||||
    #[derive(Debug, Serialize)]
 | 
			
		||||
    pub struct SubscriptionLoggedOutTemplate {
 | 
			
		||||
        pub providers: Vec<SubscriptionLoggedOutTemplateProviders>,
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn make_templates() -> TinyTemplate<'static> {
 | 
			
		||||
    let mut templates = TinyTemplate::new();
 | 
			
		||||
    for template in TEMPLATES {
 | 
			
		||||
        if let Err(e) = templates.add_template(template.name(), template.text()) {
 | 
			
		||||
            panic!("error parsing template {}: {}", template.name(), e);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    templates
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn with_templates<F: FnOnce(&TinyTemplate<'static>) -> R, R>(f: F) -> R {
 | 
			
		||||
    thread_local! {
 | 
			
		||||
        static TEMPLATES: TinyTemplate<'static> = make_templates();
 | 
			
		||||
    }
 | 
			
		||||
    TEMPLATES.with(f)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone, Deserialize, Serialize)]
 | 
			
		||||
pub struct SessionState {
 | 
			
		||||
    pub email: EndUserEmail,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl SessionState {
 | 
			
		||||
    pub fn get(session: &Session) -> Option<Self> {
 | 
			
		||||
        session
 | 
			
		||||
            .get("state")
 | 
			
		||||
            .expect("state is signed so we know it's valid")
 | 
			
		||||
    }
 | 
			
		||||
    pub fn set(&self, session: &Session) {
 | 
			
		||||
        session
 | 
			
		||||
            .insert("state", self)
 | 
			
		||||
            .expect("serialization can't fail");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl OIDCProvider {}
 | 
			
		||||
 | 
			
		||||
const NONCE: &'static str = "nonce";
 | 
			
		||||
const CSRF_TOKEN: &'static str = "csrf_token";
 | 
			
		||||
 | 
			
		||||
#[get("/subscription/login/{provider}")]
 | 
			
		||||
pub async fn login(
 | 
			
		||||
    config: web::Data<Config>,
 | 
			
		||||
    session: Session,
 | 
			
		||||
    path: web::Path<(String,)>,
 | 
			
		||||
    uri: Uri,
 | 
			
		||||
) -> impl Responder {
 | 
			
		||||
    let mut resp = HttpResponse::SeeOther().body("");
 | 
			
		||||
    resp.headers_mut()
 | 
			
		||||
        .insert(LOCATION, "/subscription".parse().unwrap());
 | 
			
		||||
    if SessionState::get(&session).is_some() {
 | 
			
		||||
        return resp;
 | 
			
		||||
    }
 | 
			
		||||
    let Some(provider) = config.oidc.get(&path.0) else {
 | 
			
		||||
        return resp;
 | 
			
		||||
    };
 | 
			
		||||
    let (url, csrf_token, nonce) = provider
 | 
			
		||||
        .state()
 | 
			
		||||
        .client
 | 
			
		||||
        .authorize_url(
 | 
			
		||||
            CoreAuthenticationFlow::AuthorizationCode,
 | 
			
		||||
            CsrfToken::new_random,
 | 
			
		||||
            Nonce::new_random,
 | 
			
		||||
        )
 | 
			
		||||
        .add_scopes(provider.scopes.clone())
 | 
			
		||||
        .url();
 | 
			
		||||
    session.insert(CSRF_TOKEN, csrf_token).unwrap();
 | 
			
		||||
    session.insert(NONCE, nonce).unwrap();
 | 
			
		||||
    resp.headers_mut()
 | 
			
		||||
        .insert(LOCATION, url.as_str().parse().unwrap());
 | 
			
		||||
    resp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[get("/subscription")]
 | 
			
		||||
pub async fn subscription(config: web::Data<Config>, session: Session) -> impl Responder {
 | 
			
		||||
    if let Some(SessionState { email }) = SessionState::get(&session) {
 | 
			
		||||
        todo!()
 | 
			
		||||
    } else {
 | 
			
		||||
        let mut template = SubscriptionLoggedOutTemplate { providers: vec![] };
 | 
			
		||||
        for (name, provider) in config.oidc.iter() {
 | 
			
		||||
            template
 | 
			
		||||
                .providers
 | 
			
		||||
                .push(SubscriptionLoggedOutTemplateProviders {
 | 
			
		||||
                    pretty_name: provider.pretty_name.clone(),
 | 
			
		||||
                    url: format!("/subscription/login/{name}"),
 | 
			
		||||
                });
 | 
			
		||||
        }
 | 
			
		||||
        HttpResponse::Ok()
 | 
			
		||||
            .content_type(ContentType::html())
 | 
			
		||||
            .body(template.render().expect("rendering can't fail"))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub fn all_services(cfg: &mut ServiceConfig) {
 | 
			
		||||
    cfg.service(subscription).service(login);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_make_templates() {
 | 
			
		||||
        make_templates();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										72
									
								
								src/cli.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/cli.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,72 @@
 | 
			
		|||
use crate::config;
 | 
			
		||||
use clap::{ArgAction, Args, Parser};
 | 
			
		||||
use eyre::ensure;
 | 
			
		||||
use futures::try_join;
 | 
			
		||||
use listenfd::ListenFd;
 | 
			
		||||
use std::{net::TcpListener, os::unix::net::UnixListener, path::PathBuf};
 | 
			
		||||
 | 
			
		||||
#[derive(Parser, Debug)]
 | 
			
		||||
pub struct CLI {
 | 
			
		||||
    #[arg(long, value_parser, value_name = "path/to/config.toml")]
 | 
			
		||||
    pub config: config::Config,
 | 
			
		||||
    #[command(flatten)]
 | 
			
		||||
    pub listen: Listen,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl CLI {
 | 
			
		||||
    pub async fn resolve(&mut self, listenfd: &mut ListenFd) -> eyre::Result<()> {
 | 
			
		||||
        try_join!(self.config.resolve(), self.listen.resolve(listenfd))?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Args, Debug)]
 | 
			
		||||
#[group(required = true)]
 | 
			
		||||
pub struct Listen {
 | 
			
		||||
    #[arg(long, value_name = "HOST:PORT")]
 | 
			
		||||
    pub listen_tcp: Vec<String>,
 | 
			
		||||
    #[arg(long, value_name = "path/to/socket.sock")]
 | 
			
		||||
    pub listen_unix: Vec<PathBuf>,
 | 
			
		||||
    #[arg(long, action = ArgAction::SetTrue, default_missing_value = "", value_parser = |_: &str| Ok::<_, String>(ListenFdSockets(vec![])))]
 | 
			
		||||
    pub listen_fd: Option<ListenFdSockets>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub struct ListenFdSockets(pub Vec<ListenFdSocket>);
 | 
			
		||||
 | 
			
		||||
impl Clone for ListenFdSockets {
 | 
			
		||||
    fn clone(&self) -> Self {
 | 
			
		||||
        panic!("not clonable")
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
pub enum ListenFdSocket {
 | 
			
		||||
    Tcp(TcpListener),
 | 
			
		||||
    Unix(UnixListener),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Listen {
 | 
			
		||||
    pub async fn resolve(&mut self, listenfd: &mut ListenFd) -> eyre::Result<()> {
 | 
			
		||||
        if let Some(listen_fd_sockets) = &mut self.listen_fd {
 | 
			
		||||
            ensure!(
 | 
			
		||||
                listenfd.len() != 0,
 | 
			
		||||
                "no file descriptor passed in through LISTEN_FDS systemd protocol"
 | 
			
		||||
            );
 | 
			
		||||
            for index in 0..listenfd.len() {
 | 
			
		||||
                let mut result = listenfd
 | 
			
		||||
                    .take_tcp_listener(index)
 | 
			
		||||
                    .transpose()
 | 
			
		||||
                    .expect("fds not yet taken")
 | 
			
		||||
                    .map(ListenFdSocket::Tcp);
 | 
			
		||||
                if result.is_err() {
 | 
			
		||||
                    if let Ok(Some(v)) = listenfd.take_unix_listener(index) {
 | 
			
		||||
                        result = Ok(ListenFdSocket::Unix(v));
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                listen_fd_sockets.0.push(result?);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										96
									
								
								src/config.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/config.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,96 @@
 | 
			
		|||
use clap::builder::{TryMapValueParser, TypedValueParser, ValueParserFactory};
 | 
			
		||||
use clio::CachedInput;
 | 
			
		||||
use eyre::Context;
 | 
			
		||||
use futures::future::try_join_all;
 | 
			
		||||
use indexmap::IndexMap;
 | 
			
		||||
use openidconnect::{
 | 
			
		||||
    core::{CoreClient, CoreProviderMetadata},
 | 
			
		||||
    ClientId, ClientSecret, IssuerUrl, Scope,
 | 
			
		||||
};
 | 
			
		||||
use serde::{Deserialize, Serialize};
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize, Serialize, Debug, Clone)]
 | 
			
		||||
pub struct Config {
 | 
			
		||||
    pub oidc: IndexMap<String, OIDCProvider>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl ValueParserFactory for Config {
 | 
			
		||||
    type Parser = TryMapValueParser<
 | 
			
		||||
        <CachedInput as ValueParserFactory>::Parser,
 | 
			
		||||
        fn(CachedInput) -> eyre::Result<Config>,
 | 
			
		||||
    >;
 | 
			
		||||
 | 
			
		||||
    fn value_parser() -> Self::Parser {
 | 
			
		||||
        CachedInput::value_parser().try_map(Config::load)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Config {
 | 
			
		||||
    pub const EXAMPLE: &'static str =
 | 
			
		||||
        include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/config.toml.sample"));
 | 
			
		||||
    pub fn load_str(input: &str, path: impl ToString) -> eyre::Result<Config> {
 | 
			
		||||
        toml::from_str(input).wrap_err_with(|| path.to_string())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub fn load(input: CachedInput) -> eyre::Result<Config> {
 | 
			
		||||
        let s = std::str::from_utf8(input.get_data()).wrap_err_with(|| input.path().to_string())?;
 | 
			
		||||
        toml::from_str(s).wrap_err_with(|| input.path().to_string())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    pub async fn resolve(&mut self) -> eyre::Result<()> {
 | 
			
		||||
        try_join_all(self.oidc.iter_mut().map(|(_, provider)| provider.resolve())).await?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, Clone)]
 | 
			
		||||
pub struct OIDCProviderState {
 | 
			
		||||
    pub client: CoreClient,
 | 
			
		||||
    pub provider_metadata: CoreProviderMetadata,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Deserialize, Serialize, Debug, Clone)]
 | 
			
		||||
pub struct OIDCProvider {
 | 
			
		||||
    pub pretty_name: String,
 | 
			
		||||
    pub issuer_url: IssuerUrl,
 | 
			
		||||
    pub client_id: ClientId,
 | 
			
		||||
    pub secret: ClientSecret,
 | 
			
		||||
    pub scopes: Vec<Scope>,
 | 
			
		||||
    #[serde(skip)]
 | 
			
		||||
    state: Option<OIDCProviderState>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl OIDCProvider {
 | 
			
		||||
    pub fn state(&self) -> &OIDCProviderState {
 | 
			
		||||
        self.state.as_ref().expect("resolve called by main")
 | 
			
		||||
    }
 | 
			
		||||
    pub async fn resolve(&mut self) -> eyre::Result<()> {
 | 
			
		||||
        let provider_metadata = CoreProviderMetadata::discover_async(
 | 
			
		||||
            self.issuer_url.clone(),
 | 
			
		||||
            openidconnect::reqwest::async_http_client,
 | 
			
		||||
        )
 | 
			
		||||
        .await?;
 | 
			
		||||
        let client = CoreClient::from_provider_metadata(
 | 
			
		||||
            provider_metadata.clone(),
 | 
			
		||||
            self.client_id.clone(),
 | 
			
		||||
            Some(self.secret.clone()),
 | 
			
		||||
        );
 | 
			
		||||
        let client = client.disable_openid_scope();
 | 
			
		||||
        self.state = Some(OIDCProviderState {
 | 
			
		||||
            client,
 | 
			
		||||
            provider_metadata,
 | 
			
		||||
        });
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
mod tests {
 | 
			
		||||
    use super::*;
 | 
			
		||||
 | 
			
		||||
    #[test]
 | 
			
		||||
    fn test_example() -> eyre::Result<()> {
 | 
			
		||||
        Config::load_str(Config::EXAMPLE, "config.toml.sample")?;
 | 
			
		||||
        Ok(())
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										54
									
								
								src/main.rs
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/main.rs
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,54 @@
 | 
			
		|||
use crate::cli::{Listen, ListenFdSocket, ListenFdSockets};
 | 
			
		||||
use actix_session::{
 | 
			
		||||
    config::{BrowserSession, SessionMiddlewareBuilder},
 | 
			
		||||
    storage::CookieSessionStore,
 | 
			
		||||
    SessionMiddleware,
 | 
			
		||||
};
 | 
			
		||||
use actix_web::{cookie::Key, web, App, HttpServer};
 | 
			
		||||
use clap::Parser;
 | 
			
		||||
use listenfd::ListenFd;
 | 
			
		||||
 | 
			
		||||
mod app;
 | 
			
		||||
mod cli;
 | 
			
		||||
mod config;
 | 
			
		||||
 | 
			
		||||
#[tokio::main]
 | 
			
		||||
async fn main() -> eyre::Result<()> {
 | 
			
		||||
    let mut listenfd = ListenFd::from_env();
 | 
			
		||||
    color_eyre::install()?;
 | 
			
		||||
    let mut args = cli::CLI::parse();
 | 
			
		||||
    args.resolve(&mut listenfd).await?;
 | 
			
		||||
    let config = web::Data::new(args.config);
 | 
			
		||||
    let cookie_session_key = Key::generate();
 | 
			
		||||
    let mut server = HttpServer::new(move || {
 | 
			
		||||
        let cookie_session =
 | 
			
		||||
            SessionMiddleware::builder(CookieSessionStore::default(), cookie_session_key.clone())
 | 
			
		||||
                .cookie_name("session".into())
 | 
			
		||||
                .build();
 | 
			
		||||
        App::new()
 | 
			
		||||
            .wrap(cookie_session)
 | 
			
		||||
            .app_data(config.clone())
 | 
			
		||||
            .configure(app::all_services)
 | 
			
		||||
    });
 | 
			
		||||
    let Listen {
 | 
			
		||||
        listen_tcp,
 | 
			
		||||
        listen_unix,
 | 
			
		||||
        listen_fd,
 | 
			
		||||
    } = &mut args.listen;
 | 
			
		||||
    for sock in listen_tcp.iter() {
 | 
			
		||||
        server = server.bind_auto_h2c(sock)?;
 | 
			
		||||
    }
 | 
			
		||||
    for sock in listen_unix.iter() {
 | 
			
		||||
        server = server.bind_uds(sock)?;
 | 
			
		||||
    }
 | 
			
		||||
    if let Some(ListenFdSockets(sockets)) = listen_fd {
 | 
			
		||||
        for sock in sockets.drain(..) {
 | 
			
		||||
            server = match sock {
 | 
			
		||||
                ListenFdSocket::Tcp(sock) => server.listen_auto_h2c(sock)?,
 | 
			
		||||
                ListenFdSocket::Unix(sock) => server.listen_uds(sock)?,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    server.run().await?;
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
		Reference in a new issue