aboutsummaryrefslogtreecommitdiffstats
path: root/crates/api-auth/src/util.rs
blob: b15a5e2fb333fee023714e11d831d0eca9d94f32 (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
98
99
100
101
102
103
104
105
106
107
use api_core::models::user::User;
use async_session::{Session, serde_json};
use oauth2::{AuthorizationCode, CsrfToken, Scope, TokenResponse};
use redis::AsyncCommands;
use serde::de::DeserializeOwned;
use sh_util::cache::{CacheKey, RedisManager};

use crate::{BasicClient, CSRF_TOKEN, SessionResponse, client::AuthHttpClient, error::AuthError};

pub async fn create_oauth_session(
    client: &BasicClient,
    cache: &RedisManager,
    scopes: &[&str],
) -> Result<SessionResponse, AuthError> {
    let mut builder = client.authorize_url(CsrfToken::new_random);

    for pat in scopes {
        builder = builder.add_scope(Scope::new(pat.to_string()));
    }
    let (auth_url, csrf_token) = builder.url();

    let mut session = Session::new();
    session.insert(CSRF_TOKEN, &csrf_token).unwrap();

    let cache_key = CacheKey::Session(session.id());
    let mut cache = cache.get().await.unwrap();
    cache
        .set::<_, _, ()>(
            cache_key,
            serde_json::to_string(&session).or(Err(AuthError::InvalidSession))?,
        )
        .await?;
    let cookie = session
        .into_cookie_value()
        .ok_or(AuthError::MissingSession)?;

    Ok(SessionResponse {
        cookie_value: cookie,
        auth_url,
    })
}

pub async fn get_user<T>(
    c: &BasicClient,
    client: &AuthHttpClient,
    code: &str,
    _endpoint: &str,
) -> Result<User, AuthError>
where
    User: TryFrom<T>,
    T: DeserializeOwned,
{
    // Get an auth token
    let token = c
        .exchange_code(AuthorizationCode::new(code.to_owned()))
        .request_async(client)
        .await
        .map_err(|_e| AuthError::UserToken)?;
    // Fetch user data from discord
    let user_data: T = client
        // https://discord.com/developers/docs/resources/user#get-current-user
        .get("https://discordapp.com/api/users/@me")
        .bearer_auth(token.access_token().secret())
        .send()
        .await
        .map_err(|_e| AuthError::UserRetrieval)?
        .json::<T>()
        .await
        .map_err(|_e| AuthError::UserDeserialisation)?;

    User::try_from(user_data).map_err(|_e| AuthError::UserDeserialisation)
}

pub async fn validate_session(
    cache: &RedisManager,
    cookie: &str,
    state: &str,
) -> Result<(), AuthError> {
    let id = Session::id_from_cookie_value(cookie)?;
    let cache_key = CacheKey::Session(&id);
    let mut cache = cache.get().await.unwrap();
    let session = cache.get::<_, String>(&cache_key).await?;
    let session: Session =
        serde_json::from_str(&session).map_err(|_e| AuthError::InvalidSession)?;

    match session.validate() {
        Some(session) => {
            // Extract the CSRF token from the session
            let stored_csrf_token = session.get::<CsrfToken>(CSRF_TOKEN);

            if let Some(stored) = stored_csrf_token {
                // Cleanup the CSRF token session
                cache.del::<_, ()>(cache_key).await?;

                // Validate CSRF token is the same as the one in the auth request
                if *stored.secret() != state {
                    Err(AuthError::TokenMismatch)
                } else {
                    Ok(())
                }
            } else {
                Err(AuthError::NoCSRFToken)
            }
        }
        None => Err(AuthError::MissingSession),
    }
}