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
108
109
110
111
112
113
114
115
116
117
118
119
use std::sync::Arc;

use bonsaidb_core::admin::{AuthenticationToken, Role, User};
use bonsaidb_core::connection::{
    IdentityId, Session, SessionAuthentication, SessionId, TokenChallengeAlgorithm,
};
use bonsaidb_core::key::time::TimestampAsNanoseconds;
use bonsaidb_core::permissions::Permissions;
use bonsaidb_core::schema::SerializedCollection;
use parking_lot::Mutex;
use rand::{thread_rng, Rng};

use crate::storage::AuthenticatedSession;
use crate::{Database, Storage};

impl super::StorageInstance {
    pub(super) fn begin_token_authentication(
        &self,
        id: u64,
        request_time: TimestampAsNanoseconds,
        request_time_check: &[u8],
        algorithm: TokenChallengeAlgorithm,
        admin: &Database,
    ) -> Result<Storage, bonsaidb_core::Error> {
        // TODO alow configuration of timestamp sensitivity. 5 minutes chosen based on Kerberos and
        if request_time
            .duration_between(&TimestampAsNanoseconds::now())?
            .as_secs()
            > 300
        {
            return Err(bonsaidb_core::Error::InvalidCredentials); // TODO better error
        }
        let token = AuthenticationToken::get(&id, admin)?
            .ok_or(bonsaidb_core::Error::InvalidCredentials)?;
        AuthenticationToken::check_request_time(
            request_time,
            request_time_check,
            algorithm,
            &token.contents.token,
        )?;

        // Token authentication creates a temporary session for the token
        // challenge. The process of finishing token authentication will remove
        // the session.

        let mut sessions = self.data.sessions.write();
        sessions.last_session_id += 1;
        let session_id = SessionId(sessions.last_session_id);
        let nonce = thread_rng().gen::<[u8; 32]>();
        let session = Session {
            id: Some(session_id),
            authentication: SessionAuthentication::TokenChallenge {
                id,
                algorithm: TokenChallengeAlgorithm::Blake3,
                nonce,
                server_timestamp: TimestampAsNanoseconds::now(),
            },
            permissions: Permissions::default(), /* This session will have no permissions until it finishes token authentication */
        };
        let authentication = Arc::new(AuthenticatedSession {
            storage: Arc::downgrade(&self.data),
            session: Mutex::new(session.clone()),
        });
        sessions.sessions.insert(session_id, authentication.clone());

        Ok(Storage {
            instance: self.clone(),
            authentication: Some(authentication),
            effective_session: Some(Arc::new(session)),
        })
    }

    pub(super) fn finish_token_authentication(
        &self,
        session_id: SessionId,
        hash: &[u8],
        admin: &Database,
    ) -> Result<Storage, bonsaidb_core::Error> {
        // Remove the temporary session so that it can't be used multiple times.
        let session = {
            let mut sessions = self.data.sessions.write();
            sessions
                .sessions
                .remove(&session_id)
                .ok_or(bonsaidb_core::Error::InvalidCredentials)?
        };
        let session = session.session.lock();
        match &session.authentication {
            SessionAuthentication::TokenChallenge {
                id,
                algorithm,
                nonce,
                server_timestamp,
            } => {
                let token = AuthenticationToken::get(id, admin)?
                    .ok_or(bonsaidb_core::Error::InvalidCredentials)?;
                token
                    .contents
                    .validate_challenge(*algorithm, *server_timestamp, nonce, hash)?;
                match token.contents.identity {
                    IdentityId::User(id) => {
                        let user = User::get(&id, admin)?
                            .ok_or(bonsaidb_core::Error::InvalidCredentials)?;
                        self.assume_user(user, admin)
                    }
                    IdentityId::Role(id) => {
                        let role = Role::get(&id, admin)?
                            .ok_or(bonsaidb_core::Error::InvalidCredentials)?;
                        self.assume_role(role, admin)
                    }
                    _ => Err(bonsaidb_core::Error::InvalidCredentials),
                }
            }
            SessionAuthentication::None | SessionAuthentication::Identity(_) => {
                Err(bonsaidb_core::Error::InvalidCredentials)
            }
        }
    }
}