summaryrefslogtreecommitdiff
path: root/server/src/auth.rs
blob: ab1ba8b4077a7a40a81c377b18dd8fbd7fe0716b (plain)
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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
use std::{
    env,
    num::NonZeroUsize,
    sync::{Arc, Mutex},
};

use lru::LruCache;
use openidconnect::{
    core::{CoreClient, CoreProviderMetadata, CoreResponseType},
    reqwest::async_http_client,
    url::Url,
    AccessTokenHash, AuthenticationFlow, AuthorizationCode, ClientId, ClientSecret, CsrfToken,
    IssuerUrl, Nonce, OAuth2TokenResponse, PkceCodeChallenge, RedirectUrl, Scope, TokenResponse,
};
use uuid::Uuid;
use serde::{Deserialize, Serialize};

pub struct LoginState {
    csrf_token: CsrfToken,
    nonce: Nonce,
}

pub struct Authenticator {
    pub client: CoreClient,
    pub login_cache: Arc<Mutex<LruCache<EndUserId, LoginState>>>,
}

#[derive(Eq, PartialEq, Hash, Debug, Clone, Serialize, Deserialize)]
pub struct EndUserId(Uuid);

impl EndUserId {
    pub fn new() -> Self {
        Self(Uuid::new_v4())
    }
}

const LOGIN_CACHE_SIZE: usize = 50;

pub const LOGIN_CALLBACK: &'static str = "/api/login_callback";
fn redirect_url(app_url: &str) -> RedirectUrl {
    RedirectUrl::new(format!("{}{}", app_url, LOGIN_CALLBACK)).unwrap()
}

impl Authenticator {
    pub async fn new(
        issuer_url: IssuerUrl,
        client_id: ClientId,
        client_secret: ClientSecret,
        redirect_uri: RedirectUrl,
    ) -> Self {
        // Use OpenID Connect Discovery to fetch the provider metadata.
        let provider_metadata = CoreProviderMetadata::discover_async(issuer_url, async_http_client)
            .await
            .unwrap();

        let client =
            CoreClient::from_provider_metadata(provider_metadata, client_id, Some(client_secret))
                // Set the URL the user will be redirected to after the authorization process.
                .set_redirect_uri(redirect_uri);

        Self {
            client,
            login_cache: Arc::new(Mutex::new(LruCache::new(
                NonZeroUsize::new(LOGIN_CACHE_SIZE).unwrap(),
            ))),
        }
    }

    pub async fn from_env() -> Self {
        let app_url = env::var("APP_URL").unwrap();
        Authenticator::new(
            IssuerUrl::new(env::var("OPENID_ISSUER_URL").unwrap()).unwrap(),
            ClientId::new(env::var("OPENID_CLIENT_ID").unwrap()),
            ClientSecret::new(env::var("OPENID_CLIENT_SECRET").unwrap()),
            redirect_url(&app_url),
        )
        .await
    }

    pub async fn get_login_url(&self) -> (EndUserId, Url) {
        let (auth_url, csrf_token, nonce) = self
            .client
            .authorize_url(
                AuthenticationFlow::<CoreResponseType>::AuthorizationCode,
                CsrfToken::new_random,
                Nonce::new_random,
            )
            .add_scope(Scope::new("email".to_string()))
            .add_scope(Scope::new("profile".to_string()))
            .url();
        let user_id = EndUserId::new();
        self.login_cache
            .lock().unwrap()
            .put(user_id.clone(), LoginState { csrf_token, nonce });
        (user_id, auth_url)
    }
}