mod manifest;
mod middleware;
mod oauth2;
mod v1;

use self::manifest::manifest;
use self::middleware::*;
use self::oauth2::*;
use self::v1::*;

use compact_jwt::{Jws, JwsSigner, JwsUnverified, JwsValidator};
use kanidm::actors::v1_read::QueryServerReadV1;
use kanidm::actors::v1_write::QueryServerWriteV1;
use kanidm::config::{ServerRole, TlsConfiguration};
use kanidm::prelude::*;
use kanidm::status::StatusActor;
use kanidm::tracing_tree::TreeMiddleware;
use regex::Regex;
use serde::Serialize;
use std::fs::canonicalize;
use std::path::PathBuf;
use std::str::FromStr;
use tide_compress::CompressMiddleware;
use tide_openssl::TlsListener;
use tracing::{error, info};
use uuid::Uuid;

#[derive(Clone)]
pub struct AppState {
    pub status_ref: &'static StatusActor,
    pub qe_w_ref: &'static QueryServerWriteV1,
    pub qe_r_ref: &'static QueryServerReadV1,
    // Store the token management parts.
    pub jws_signer: std::sync::Arc<JwsSigner>,
    pub jws_validator: std::sync::Arc<JwsValidator>,
}

/// This is for the tide_compression middleware so that we only compress certain content types.
///
/// ```
/// use score::https::compression_content_type_checker;
/// let these_should_match = vec![
///     "application/wasm",
///     "text/json",
///     "text/javascript"
/// ];
/// for test_value in these_should_match {
///     eprintln!("checking {:?}", test_value);
///     assert!(compression_content_type_checker().is_match(test_value));
/// }
/// assert!(compression_content_type_checker().is_match("application/wasm"));
/// let these_should_fail = vec![
///     "image/jpeg",
///     "image/wasm",
///     "text/html",
/// ];
/// for test_value in these_should_fail {
///     eprintln!("checking {:?}", test_value);
///     assert!(!compression_content_type_checker().is_match(test_value));
/// }
/// ```
pub fn compression_content_type_checker() -> Regex {
    Regex::new(r"^(?:(image/svg\+xml)|(?:application|text)/(?:css|javascript|json|text|xml|wasm))$")
        .expect("regex matcher for tide_compress content-type check failed to compile")
}
pub trait RequestExtensions {
    fn get_current_uat(&self) -> Option<String>;

    fn get_current_auth_session_id(&self) -> Option<Uuid>;

    fn get_url_param(&self, param: &str) -> Result<String, tide::Error>;

    fn new_eventid(&self) -> (Uuid, String);
}

impl RequestExtensions for tide::Request<AppState> {
    fn get_current_uat(&self) -> Option<String> {
        // Contact the QS to get it to validate wtf is up.
        // let kref = &self.state().bundy_handle;
        // self.session().get::<UserAuthToken>("uat")
        self.header(tide::http::headers::AUTHORIZATION)
            .and_then(|hv| {
                // Get the first header value.
                hv.get(0)
            })
            .and_then(|h| {
                // Turn it to a &str, and then check the prefix
                h.as_str().strip_prefix("Bearer ")
            })
            .map(|s| s.to_string())
            .or_else(|| self.session().get::<String>("bearer"))
        /*
        .and_then(|ts| {
            // Take the token str and attempt to decrypt
            // Attempt to re-inflate a UAT from bytes.
            //
            // NOTE: UAT expiry validation is performed in event.rs!
            let uat: Option<UserAuthToken> = kref.verify(ts).ok();
            uat
        })
        */
    }

    fn get_current_auth_session_id(&self) -> Option<Uuid> {
        // We see if there is a signed header copy first.
        let kref = &self.state().jws_validator;
        self.header("X-KANIDM-AUTH-SESSION-ID")
            .and_then(|hv| {
                // Get the first header value.
                hv.get(0)
            })
            .and_then(|h| {
                // Take the token str and attempt to decrypt
                // Attempt to re-inflate a uuid from bytes.
                JwsUnverified::from_str(h.as_str()).ok()
            })
            .and_then(|jwsu| {
                jwsu.validate(kref)
                    .map(|jws: Jws<SessionId>| jws.inner.sessionid)
                    .ok()
            })
            // If not there, get from the cookie instead.
            .or_else(|| self.session().get::<Uuid>("auth-session-id"))
    }

    fn get_url_param(&self, param: &str) -> Result<String, tide::Error> {
        self.param(param).map(str::to_string).map_err(|e| {
            error!(?e);
            tide::Error::from_str(tide::StatusCode::ImATeapot, "teapot")
        })
    }

    fn new_eventid(&self) -> (Uuid, String) {
        let eventid = kanidm::tracing_tree::operation_id().unwrap();
        let hv = eventid.as_hyphenated().to_string();
        (eventid, hv)
    }
}

pub fn to_tide_response<T: Serialize>(
    v: Result<T, OperationError>,
    hvalue: String,
) -> tide::Result {
    match v {
        Ok(iv) => {
            let mut res = tide::Response::new(200);
            tide::Body::from_json(&iv).map(|b| {
                res.set_body(b);
                res
            })
        }
        Err(e) => {
            let mut res = match &e {
                OperationError::NotAuthenticated | OperationError::SessionExpired => {
                    // https://datatracker.ietf.org/doc/html/rfc7235#section-4.1
                    let mut res = tide::Response::new(tide::StatusCode::Unauthorized);
                    res.insert_header("WWW-Authenticate", "Bearer");
                    res
                }
                OperationError::SystemProtectedObject | OperationError::AccessDenied => {
                    tide::Response::new(tide::StatusCode::Forbidden)
                }
                OperationError::NoMatchingEntries => {
                    tide::Response::new(tide::StatusCode::NotFound)
                }
                OperationError::PasswordQuality(_)
                | OperationError::EmptyRequest
                | OperationError::SchemaViolation(_) => {
                    tide::Response::new(tide::StatusCode::BadRequest)
                }
                _ => tide::Response::new(tide::StatusCode::InternalServerError),
            };
            tide::Body::from_json(&e).map(|b| {
                res.set_body(b);
                res
            })
        }
    }
    .map(|mut res| {
        res.insert_header("X-KANIDM-OPID", hvalue);
        res
    })
}

/// Returns a generic robots.txt
async fn robots_txt(_req: tide::Request<AppState>) -> tide::Result {
    let mut res = tide::Response::new(200);

    res.set_content_type("text/plain");
    res.set_body(
        r#"
User-agent: *
Disallow: /
"#,
    );
    Ok(res)
}

/// The web UI at / for Kanidm
async fn index_view(req: tide::Request<AppState>) -> tide::Result {
    let mut res = tide::Response::new(200);
    let (eventid, hvalue) = req.new_eventid();

    let domain_display_name = req.state().qe_r_ref.get_domain_display_name(eventid).await;
    res.insert_header("X-KANIDM-OPID", hvalue);

    res.set_content_type("text/html;charset=utf-8");
    res.set_body(format!(r#"
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8"/>
        <meta name="theme-color" content="white" />
        <meta name="viewport" content="width=device-width" />
        <title>{}</title>

        <link rel="icon" href="/pkg/img/favicon.png" />
        <link rel="manifest" href="/manifest.webmanifest" />
        <link rel="apple-touch-icon" href="/pkg/img/logo-256.png" />
        <link rel="apple-touch-icon" sizes="180x180" href="/pkg/img/logo-180.png" />
        <link rel="apple-touch-icon" sizes="192x192" href="/pkg/img/logo-192.png" />
        <link rel="apple-touch-icon" sizes="512x512" href="/pkg/img/logo-square.svg" />
        <link rel="stylesheet" href="/pkg/external/bootstrap.min.css" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"/>
        <link rel="stylesheet" href="/pkg/style.css"/>

        <script src="/pkg/external/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"></script>
        <script type="module" type="text/javascript" src="/pkg/wasmloader.js" integrity="sha384-==WASMHASH=="></script>
    </head>
    <body class="flex-column d-flex h-100">
        <main class="flex-shrink-0 form-signin">
        <center>
            <img src="/pkg/img/logo-square.svg" alt="Kanidm" class="kanidm_logo"/>
            <h3>Kanidm is loading, please wait... </h3>
        </center>
        </main>
        <footer class="footer mt-auto py-3 bg-light text-end">
            <div class="container">
                <span class="text-muted">Powered by <a href="https://kanidm.com">Kanidm</a></span>
            </div>
        </footer>
    </body>
</html>"#, domain_display_name.as_str()
    )
    );

    Ok(res)
}

#[derive(Default)]
struct NoCacheMiddleware;

#[async_trait::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for NoCacheMiddleware {
    async fn handle(
        &self,
        request: tide::Request<State>,
        next: tide::Next<'_, State>,
    ) -> tide::Result {
        let mut response = next.run(request).await;
        response.insert_header("Cache-Control", "no-store, max-age=0");
        response.insert_header("Pragma", "no-cache");
        Ok(response)
    }
}

#[derive(Default)]
struct CacheableMiddleware;

#[async_trait::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for CacheableMiddleware {
    async fn handle(
        &self,
        request: tide::Request<State>,
        next: tide::Next<'_, State>,
    ) -> tide::Result {
        let mut response = next.run(request).await;
        response.insert_header("Cache-Control", "max-age=60,must-revalidate,private");
        Ok(response)
    }
}

#[derive(Default)]
/// Sets Cache-Control headers on static content endpoints
struct StaticContentMiddleware;

#[async_trait::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for StaticContentMiddleware {
    async fn handle(
        &self,
        request: tide::Request<State>,
        next: tide::Next<'_, State>,
    ) -> tide::Result {
        let mut response = next.run(request).await;
        response.insert_header("Cache-Control", "max-age=3600,private");
        Ok(response)
    }
}

#[derive(Default)]
/// Adds the folloing headers to responses
/// - x-frame-options
/// - x-content-type-options
/// - cross-origin-resource-policy
/// - cross-origin-embedder-policy
/// - cross-origin-opener-policy
struct StrictResponseMiddleware;

#[async_trait::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for StrictResponseMiddleware {
    async fn handle(
        &self,
        request: tide::Request<State>,
        next: tide::Next<'_, State>,
    ) -> tide::Result {
        let mut response = next.run(request).await;
        response.insert_header("cross-origin-embedder-policy", "require-corp");
        response.insert_header("cross-origin-opener-policy", "same-origin");
        response.insert_header("cross-origin-resource-policy", "same-origin");
        response.insert_header("x-content-type-options", "nosniff");
        response.insert_header("x-frame-options", "deny");
        Ok(response)
    }
}
struct StrictRequestMiddleware;

impl Default for StrictRequestMiddleware {
    fn default() -> Self {
        StrictRequestMiddleware {}
    }
}

#[async_trait::async_trait]
impl<State: Clone + Send + Sync + 'static> tide::Middleware<State> for StrictRequestMiddleware {
    async fn handle(
        &self,
        request: tide::Request<State>,
        next: tide::Next<'_, State>,
    ) -> tide::Result {
        let proceed = request
            .header("sec-fetch-site")
            .map(|hv| {
                matches!(hv.as_str(), "same-origin" | "same-site" | "none")
                    || (request.header("sec-fetch-mode").map(|v| v.as_str()) == Some("navigate")
                        && request.method() == tide::http::Method::Get
                        && request.header("sec-fetch-dest").map(|v| v.as_str()) != Some("object")
                        && request.header("sec-fetch-dest").map(|v| v.as_str()) != Some("embed"))
            })
            .unwrap_or(true);

        if proceed {
            Ok(next.run(request).await)
        } else {
            Err(tide::Error::from_str(
                tide::StatusCode::MethodNotAllowed,
                "StrictRequestViolation",
            ))
        }
    }
}

pub fn generate_integrity_hash(filename: String) -> Result<String, String> {
    let wasm_filepath = PathBuf::from(filename);
    match wasm_filepath.exists() {
        false => {
            return Err(format!(
                "Can't find {:?} to generate file hash",
                &wasm_filepath
            ));
        }
        true => {
            let filecontents = match std::fs::read(&wasm_filepath) {
                Ok(value) => value,
                Err(error) => {
                    return Err(format!(
                        "Failed to read {:?}, skipping: {:?}",
                        wasm_filepath, error
                    ));
                }
            };
            let shasum =
                openssl::hash::hash(openssl::hash::MessageDigest::sha384(), &filecontents).unwrap();
            Ok(format!("{}", openssl::base64::encode_block(&shasum)))
        }
    }
}

// TODO: Add request limits.
pub fn create_https_server(
    address: String,
    // opt_tls_params: Option<SslAcceptorBuilder>,
    opt_tls_params: Option<&TlsConfiguration>,
    role: ServerRole,
    cookie_key: &[u8; 32],
    jws_signer: JwsSigner,
    status_ref: &'static StatusActor,
    qe_w_ref: &'static QueryServerWriteV1,
    qe_r_ref: &'static QueryServerReadV1,
) -> Result<(), ()> {
    let jws_validator = jws_signer.get_validator().map_err(|e| {
        error!(?e, "Failed to get jws validator");
    })?;

    let jws_validator = std::sync::Arc::new(jws_validator);
    let jws_signer = std::sync::Arc::new(jws_signer);

    let mut tserver = tide::Server::with_state(AppState {
        status_ref,
        qe_w_ref,
        qe_r_ref,
        jws_signer,
        jws_validator,
    });

    // tide::log::with_level(tide::log::LevelFilter::Debug);

    // Add middleware?
    tserver.with(TreeMiddleware::with_stdout());
    // tserver.with(tide::log::LogMiddleware::new());
    // We do not force a session ttl, because we validate this elsewhere in usage.
    tserver.with(
        // We do not force a session ttl, because we validate this elsewhere in usage.
        tide::sessions::SessionMiddleware::new(tide::sessions::MemoryStore::new(), cookie_key)
            .with_cookie_name("kanidm-session")
            .with_same_site_policy(tide::http::cookies::SameSite::Strict),
    );
    tserver.with(StrictResponseMiddleware::default());

    // Add routes
    // ==== static content routes that have a longer cache policy.

    // If we are no-ui, we remove this.
    if !matches!(role, ServerRole::WriteReplicaNoUI) {
        let pkg_path = PathBuf::from(env!("KANIDM_WEB_UI_PKG_PATH"));
        if !pkg_path.exists() {
            eprintln!(
                "Couldn't find Web UI package path: ({}), quitting.",
                env!("KANIDM_WEB_UI_PKG_PATH")
            );
            std::process::exit(1);
        }
        info!("Web UI package path: {:?}", canonicalize(pkg_path).unwrap());

        /*
        Let's build a compression middleware!

        The threat of the TLS BREACH attack [1] was considered as part of adding
        the CompressMiddleware configuration.

        The attack targets secrets compressed and encrypted in flight with the intent
        to infer their content.

        This is not a concern for the paths covered by this configuration
        ( /, /ui/<and all sub-paths>, /pkg/<and all sub-paths> ),
        as they're all static content with no secrets in transit - all that data should
        come from Kanidm's REST API, which is on a different path and not covered by
        the compression middleware.


        [1] - https://resources.infosecinstitute.com/topic/the-breach-attack/
        */

        let compress_middleware = CompressMiddleware::builder()
            .threshold(1024)
            .content_type_check(Some(compression_content_type_checker()))
            .build();

        let mut static_tserver = tserver.at("");
        static_tserver.with(StaticContentMiddleware::default());

        static_tserver.with(UIContentSecurityPolicyResponseMiddleware::new(
            generate_integrity_hash(env!("KANIDM_WEB_UI_PKG_PATH").to_owned() + "/wasmloader.js")
                .unwrap(),
        ));

        // The compression middleware needs to be the last one added before routes
        static_tserver.with(compress_middleware.clone());

        static_tserver.at("/").get(index_view);
        static_tserver.at("/robots.txt").get(robots_txt);
        static_tserver.at("/manifest.webmanifest").get(manifest);
        static_tserver.at("/ui/").get(index_view);
        static_tserver.at("/ui/*").get(index_view);

        let mut static_dir_tserver = tserver.at("");
        static_dir_tserver.with(StaticContentMiddleware::default());
        // The compression middleware needs to be the last one added before routes
        static_dir_tserver.with(compress_middleware);
        static_dir_tserver
            .at("/pkg")
            .serve_dir(env!("KANIDM_WEB_UI_PKG_PATH"))
            .map_err(|e| {
                error!(
                    "Failed to serve pkg dir {} -> {:?}",
                    env!("KANIDM_WEB_UI_PKG_PATH"),
                    e
                );
            })?;
    };

    // ==== Some routes can be cached - these are here:
    let mut tserver_cacheable = tserver.at("");
    tserver_cacheable.with(CacheableMiddleware::default());

    let mut account_route_cacheable = tserver_cacheable.at("/v1/account");

    // We allow caching of the radius token.
    account_route_cacheable
        .at("/:id/_radius/_token")
        .get(account_get_id_radius_token);
    // We allow clients to cache the unix token.
    account_route_cacheable
        .at("/:id/_unix/_token")
        .get(account_get_id_unix_token);

    // ==== These routes can not be cached
    let mut appserver = tserver.at("");
    appserver.with(NoCacheMiddleware::default());

    // let mut well_known = appserver.at("/.well-known");

    appserver.at("/status").get(self::status);
    // == oauth endpoints.

    let mut oauth2_process = appserver.at("/oauth2");
    // ⚠️  ⚠️   WARNING  ⚠️  ⚠️
    // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
    oauth2_process
        .at("/authorise")
        .post(oauth2_authorise_post)
        .get(oauth2_authorise_get);
    // ⚠️  ⚠️   WARNING  ⚠️  ⚠️
    // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
    oauth2_process
        .at("/authorise/permit")
        .post(oauth2_authorise_permit_post)
        .get(oauth2_authorise_permit_get);
    // ⚠️  ⚠️   WARNING  ⚠️  ⚠️
    // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
    oauth2_process
        .at("/authorise/reject")
        .post(oauth2_authorise_reject_post)
        .get(oauth2_authorise_reject_get);
    // ⚠️  ⚠️   WARNING  ⚠️  ⚠️
    // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
    oauth2_process.at("/token").post(oauth2_token_post);
    // ⚠️  ⚠️   WARNING  ⚠️  ⚠️
    // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
    oauth2_process
        .at("/token/introspect")
        .post(oauth2_token_introspect_post);

    let mut openid_process = appserver.at("/oauth2/openid");
    // ⚠️  ⚠️   WARNING  ⚠️  ⚠️
    // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
    openid_process
        .at("/:client_id/.well-known/openid-configuration")
        .get(oauth2_openid_discovery_get);
    // ⚠️  ⚠️   WARNING  ⚠️  ⚠️
    // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
    openid_process
        .at("/:client_id/userinfo")
        .get(oauth2_openid_userinfo_get);
    // ⚠️  ⚠️   WARNING  ⚠️  ⚠️
    // IF YOU CHANGE THESE VALUES YOU MUST UPDATE OIDC DISCOVERY URLS
    openid_process
        .at("/:client_id/public_key.jwk")
        .get(oauth2_openid_publickey_get);

    let mut raw_route = appserver.at("/v1/raw");
    raw_route.at("/create").post(create);
    raw_route.at("/modify").post(modify);
    raw_route.at("/delete").post(delete);
    raw_route.at("/search").post(search);

    appserver.at("/v1/auth").post(auth);
    appserver.at("/v1/auth/valid").get(auth_valid);

    let mut schema_route = appserver.at("/v1/schema");
    schema_route.at("/").get(schema_get);
    schema_route
        .at("/attributetype")
        .get(schema_attributetype_get)
        .post(do_nothing);
    schema_route
        .at("/attributetype/:id")
        .get(schema_attributetype_get_id)
        .put(do_nothing)
        .patch(do_nothing);

    schema_route
        .at("/classtype")
        .get(schema_classtype_get)
        .post(do_nothing);
    schema_route
        .at("/classtype/:id")
        .get(schema_classtype_get_id)
        .put(do_nothing)
        .patch(do_nothing);

    let mut oauth2_route = appserver.at("/v1/oauth2");
    oauth2_route.at("/").get(oauth2_get);

    oauth2_route.at("/_basic").post(oauth2_basic_post);

    oauth2_route
        .at("/:id")
        .get(oauth2_id_get)
        // It's not really possible to replace this wholesale.
        // .put(oauth2_id_put)
        .patch(oauth2_id_patch)
        .delete(oauth2_id_delete);

    oauth2_route
        .at("/:id/_scopemap/:group")
        .post(oauth2_id_scopemap_post)
        .delete(oauth2_id_scopemap_delete);

    let mut self_route = appserver.at("/v1/self");
    self_route.at("/").get(whoami);

    self_route.at("/_attr/:attr").get(do_nothing);
    self_route.at("/_credential").get(do_nothing);

    self_route
        .at("/_credential/primary/set_password")
        .post(idm_account_set_password);
    self_route.at("/_credential/:cid/_lock").get(do_nothing);

    self_route
        .at("/_radius")
        .get(do_nothing)
        .delete(do_nothing)
        .post(do_nothing);

    self_route.at("/_radius/_config").post(do_nothing);
    self_route.at("/_radius/_config/:token").get(do_nothing);
    self_route
        .at("/_radius/_config/:token/apple")
        .get(do_nothing);

    let mut person_route = appserver.at("/v1/person");
    person_route.at("/").get(person_get).post(person_post);
    person_route.at("/:id").get(person_id_get);

    let mut account_route = appserver.at("/v1/account");
    account_route.at("/").get(account_get).post(account_post);
    account_route
        .at("/:id")
        .get(account_id_get)
        .delete(account_id_delete);
    account_route
        .at("/:id/_attr/:attr")
        .get(account_id_get_attr)
        .put(account_id_put_attr)
        .post(account_id_post_attr)
        .delete(account_id_delete_attr);
    account_route
        .at("/:id/_person/_extend")
        .post(account_post_id_person_extend);
    account_route
        .at("/:id/_person/_set")
        .post(account_post_id_person_set);
    account_route.at("/:id/_lock").get(do_nothing);

    account_route.at("/:id/_credential").get(do_nothing);
    account_route
        .at("/:id/_credential/_status")
        .get(account_get_id_credential_status);
    account_route
        .at("/:id/_credential/primary")
        .put(account_put_id_credential_primary);
    account_route
        .at("/:id/_credential/:cid/_lock")
        .get(do_nothing);
    account_route
        .at("/:id/_credential/:cid/backup_code")
        .get(account_get_backup_code);
    // .post(account_post_backup_code_regenerate) // use "/:id/_credential/primary" instead
    // .delete(account_delete_backup_code); // same as above
    account_route
        .at("/:id/_credential/_update")
        .get(account_get_id_credential_update);
    account_route
        .at("/:id/_credential/_update_intent")
        .get(account_get_id_credential_update_intent);
    account_route
        .at("/:id/_credential/_update_intent/:ttl")
        .get(account_get_id_credential_update_intent);

    account_route
        .at("/:id/_ssh_pubkeys")
        .get(account_get_id_ssh_pubkeys)
        .post(account_post_id_ssh_pubkey);

    account_route
        .at("/:id/_ssh_pubkeys/:tag")
        .get(account_get_id_ssh_pubkey_tag)
        .delete(account_delete_id_ssh_pubkey_tag);

    account_route
        .at("/:id/_radius")
        .get(account_get_id_radius)
        .post(account_post_id_radius_regenerate)
        .delete(account_delete_id_radius);

    account_route.at("/:id/_unix").post(account_post_id_unix);
    account_route
        .at("/:id/_unix/_auth")
        .post(account_post_id_unix_auth);
    account_route
        .at("/:id/_unix/_credential")
        .put(account_put_id_unix_credential)
        .delete(account_delete_id_unix_credential);

    let mut cred_route = appserver.at("/v1/credential");
    cred_route
        .at("/_exchange_intent")
        .post(credential_update_exchange_intent);

    cred_route.at("/_status").post(credential_update_status);

    cred_route.at("/_update").post(credential_update_update);

    cred_route.at("/_commit").post(credential_update_commit);

    let mut group_route = appserver.at("/v1/group");
    group_route.at("/").get(group_get).post(group_post);
    group_route
        .at("/:id")
        .get(group_id_get)
        .delete(group_id_delete);
    group_route
        .at("/:id/_attr/:attr")
        .delete(group_id_delete_attr)
        .get(group_id_get_attr)
        .put(group_id_put_attr)
        .post(group_id_post_attr);
    group_route.at("/:id/_unix").post(group_post_id_unix);
    group_route
        .at("/:id/_unix/_token")
        .get(group_get_id_unix_token);

    let mut domain_route = appserver.at("/v1/domain");
    domain_route.at("/").get(domain_get);
    domain_route
        .at("/_attr/:attr")
        .get(domain_get_attr)
        .put(domain_put_attr)
        .delete(domain_delete_attr);

    let mut recycle_route = appserver.at("/v1/recycle_bin");
    recycle_route.at("/").get(recycle_bin_get);
    recycle_route.at("/:id").get(recycle_bin_id_get);
    recycle_route
        .at("/:id/_revive")
        .post(recycle_bin_revive_id_post);

    let mut accessprof_route = appserver.at("/v1/access_profile");
    accessprof_route.at("/").get(do_nothing);
    accessprof_route.at("/:id").get(do_nothing);
    accessprof_route.at("/:id/_attr/:attr").get(do_nothing);

    // ===  End routes

    // Create listener?
    match opt_tls_params {
        Some(tls_param) => {
            let tlsl = TlsListener::build()
                .addrs(&address)
                .cert(&tls_param.chain)
                .key(&tls_param.key)
                .finish()
                .map_err(|e| {
                    error!("Failed to build TLS Listener -> {:?}", e);
                })?;
            /*
            let x = Box::new(tls_param.build());
            let x_ref = Box::leak(x);
            let tlsl = TlsListener::new(address, x_ref);
            */

            tokio::spawn(async move {
                if let Err(e) = tserver.listen(tlsl).await {
                    error!(
                        "Failed to start server listener on address {:?} -> {:?}",
                        &address, e
                    );
                }
            });
        }
        None => {
            // Create without https
            tokio::spawn(async move {
                if let Err(e) = tserver.listen(&address).await {
                    error!(
                        "Failed to start server listener on address {:?} -> {:?}",
                        &address, e,
                    );
                }
            });
        }
    };
    Ok(())
}