dittolive_ditto/identity/auth/
authenticator.rs

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
120
121
122
123
124
125
126
127
128
129
130
131
use crate::{
    ditto::{Ditto, DittoFields},
    error::{DittoError, ErrorKind},
    utils::prelude::*,
};

/// Feedback provided by an authentication provider.
///
/// This feedback may include arbitrary JSON, and may be used to
/// give details about an authentication success or explain why it was rejected.
#[derive(Clone, Debug)]
pub struct AuthenticationClientFeedback {
    /// If present, this object contains feedback from the authentication provider.
    pub feedback: Option<serde_json::Value>,
}

impl RefCounted for DittoAuthenticator {}

/// Use [`ditto.auth()`] to manage authentication when using applicable identites.
///
/// The `DittoAuthenticator` is available when using the [`OnlinePlayground`]
/// and [`OnlineWithAuthentication`] identities.
///
/// [`ditto.auth()`]: crate::Ditto::auth
#[derive(Clone)]
pub struct DittoAuthenticator {
    pub(crate) ditto_fields: std::sync::Weak<DittoFields>,
}

impl DittoAuthenticator {
    #[doc(hidden)]
    #[deprecated(note = "Use `ditto.auth()` instead")]
    #[cfg(not(test))] // this dangling pattern is error-prone, ensure we don't actually call this.
    pub fn new() -> Self {
        DittoAuthenticator {
            // This is a *dangling* back reference, that used to act as a placeholder.
            // We don't use this error-prone pattern anymore, this function is just there for the
            // sake of back-compat (perhaps unnecessarily so).
            ditto_fields: std::sync::Weak::<DittoFields>::new(),
        }
    }

    /// Asks the [`Ditto`] instance to make an auth request to the configured
    /// [`Identity`]'s auth URL with a single token parameter.
    ///
    /// - `token`: An auth or API token you have configured.
    /// - `provider`: The name of an authentication provider web hook you have configured in Ditto.
    ///   Cloud, which will accept the `token` and contact your Auth service
    pub fn login(
        &self,
        token: &str,
        provider: &str,
    ) -> Result<AuthenticationClientFeedback, DittoError> {
        let fields = self
            .ditto_fields
            .upgrade()
            .ok_or(ErrorKind::ReleasedDittoInstance)?;
        let c_token = char_p::new(token);
        let c_provider = char_p::new(provider);
        let result = ffi_sdk::ditto_auth_client_login_with_token_and_feedback(
            &fields.ditto,
            c_token.as_ref(),
            c_provider.as_ref(),
        );
        fn parse_client_info(c: Option<char_p::Box>) -> AuthenticationClientFeedback {
            AuthenticationClientFeedback {
                feedback: c.map(|it| serde_json::from_str(it.to_str()).unwrap()),
            }
        }

        match result.status_code {
            0 => Ok(parse_client_info(result.c_string)),
            _ => Err(DittoError::from_authentication_feedback(parse_client_info(
                result.c_string,
            ))),
        }
    }

    #[doc(hidden)]
    #[deprecated(note = "Use `ditto.auth().login(...)` instead")]
    pub fn login_with_token_and_feedback(
        &self,
        token: &str,
        provider: &str,
    ) -> Result<AuthenticationClientFeedback, DittoError> {
        self.login(token, provider)
    }

    /// Log out of Ditto.
    ///
    /// Shutdown all replication sessions and remove any cached authentication credentials. This
    /// does *not* remove the local data store.
    pub fn logout<R>(&self, cleanup: impl FnOnce(Ditto) -> R) -> Result<R, DittoError> {
        let fields = self
            .ditto_fields
            .upgrade()
            .ok_or(ErrorKind::ReleasedDittoInstance)?;
        let status = ffi_sdk::ditto_auth_client_logout(&fields.ditto);
        if status != 0 {
            return Err(DittoError::from_ffi(ErrorKind::Authentication));
        }
        let ditto = Ditto::new_temp(fields);
        ditto.stop_sync();
        let ret = cleanup(ditto);
        Ok(ret)
    }

    /// Query whether Ditto has a valid authentication token.
    ///
    /// This will only be `true` when using an
    /// [`OnlineWithAuthentication`] identity, after a
    /// successful login. If the authentication token is allowed to expire then it will return
    /// `false` instead.
    pub fn is_authenticated(&self) -> bool {
        match self.ditto_fields.upgrade() {
            None => false,
            Some(fields) => ffi_sdk::ditto_auth_client_is_web_valid(&fields.ditto) != 0,
        }
    }

    /// The currently logged-in user ID.
    ///
    /// This will return `None` if there is no valid authentication or an
    /// [`OnlineWithAuthentication`] identity is not being
    /// used.
    pub fn user_id(&self) -> Option<String> {
        let fields = self.ditto_fields.upgrade()?;
        let c_msg = ffi_sdk::ditto_auth_client_user_id(&fields.ditto)?;
        Some(c_msg.into_string())
    }
}