1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// SPDX-FileCopyrightText: 2022 Profian Inc. <opensource@profian.com>
// SPDX-License-Identifier: AGPL-3.0-only

use super::{handle, App, Store, TlsConfig};

use anyhow::{anyhow, Context};
use async_std::fs::File;
use async_std::sync::Arc;
use axum::handler::Handler;
use axum::{Extension, Router};
use cap_async_std::fs_utf8::Dir;
use cap_async_std::path::Path;
use futures::lock::Mutex;
use futures::TryFutureExt;
use futures_rustls::TlsAcceptor;
use openidconnect::core::{CoreClient, CoreProviderMetadata};
use openidconnect::ureq::http_client;
use openidconnect::url::Url;
use openidconnect::{AuthType, ClientId, ClientSecret, IssuerUrl};

/// OpenID Connect client configuration.
pub struct OidcConfig {
    pub label: String,
    pub issuer: Url,
    pub client_id: String,
    pub client_secret: Option<String>,
}

/// [App] builder.
pub struct Builder<S> {
    store: S,
    tls: TlsConfig,
    oidc: OidcConfig,
}

impl<S: AsRef<Path>> Builder<S> {
    /// Constructs a new [Builder].
    pub fn new(store: S, tls: TlsConfig, oidc: OidcConfig) -> Self {
        Self { store, tls, oidc }
    }

    /// Builds the application and returns Drawbridge instance as a [tower::MakeService].
    pub async fn build(self) -> anyhow::Result<App> {
        let store_path = self.store.as_ref();
        let store = File::open(store_path)
            .and_then(|f| Store::new(Dir::from_std_file(f), self.oidc.label))
            .await
            .context(anyhow!(
                "failed to open store at `{}`",
                store_path.to_string_lossy()
            ))?;

        let oidc_md =
            CoreProviderMetadata::discover(&IssuerUrl::from_url(self.oidc.issuer), http_client)
                .context("failed to discover provider metadata")?;
        let oidc = CoreClient::from_provider_metadata(
            oidc_md,
            ClientId::new(self.oidc.client_id),
            self.oidc.client_secret.map(ClientSecret::new),
        )
        .set_auth_type(AuthType::RequestBody);

        Ok(App {
            make_service: Mutex::new(
                Router::new()
                    .fallback(handle.into_service())
                    .layer(Extension(Arc::new(store)))
                    .layer(Extension(oidc))
                    .into_make_service(),
            ),
            tls: TlsAcceptor::from(Arc::new(self.tls.into())),
        })
    }
}