Skip to main content

parse_rust_server/
state.rs

1//! Server state: config plus the storage adapter.
2//!
3//! Concrete over `MongoAdapter` rather than generic or boxed. 0.1.0 is Mongo only, and a type
4//! parameter threaded through every handler would be noise until a second backend exists. The
5//! `StorageAdapter` trait is still what the pipelines are written against, so swapping this for a
6//! generic later is a change in one file rather than in every route.
7
8use std::sync::Arc;
9
10use parse_rust_mongo::MongoAdapter;
11use parse_rust_rest::AclScope;
12
13use crate::config::ServerConfig;
14use crate::sessions::SessionStore;
15
16#[derive(Clone)]
17pub struct AppState {
18    config: Arc<ServerConfig>,
19    storage: Arc<MongoAdapter>,
20    sessions: Arc<SessionStore>,
21}
22
23impl AppState {
24    pub fn new(config: ServerConfig, storage: MongoAdapter) -> Self {
25        Self {
26            config: Arc::new(config),
27            storage: Arc::new(storage),
28            sessions: Arc::new(SessionStore::default()),
29        }
30    }
31
32    pub fn config(&self) -> &ServerConfig {
33        &self.config
34    }
35
36    pub fn storage(&self) -> &MongoAdapter {
37        &self.storage
38    }
39
40    pub fn sessions(&self) -> &SessionStore {
41        &self.sessions
42    }
43
44    /// Create the indexes parse-server creates at boot.
45    ///
46    /// **The names are contract, not housekeeping.** Both adapters recover `duplicated_field` by
47    /// regex over the index name, and the Mongo regex matches only auto-generated `<field>_1`
48    /// names, so passing `None` here (which lets the driver auto-name) is what makes a username
49    /// collision surface as 202 `USERNAME_TAKEN` rather than a bare 137. Naming them ourselves
50    /// would silently change the error a client sees.
51    ///
52    /// Upstream gates each of these behind a `databaseOptions.createIndexUser*` flag. Those are
53    /// not modeled yet; the indexes are unconditional here, which is the default behavior.
54    pub async fn ensure_indexes(&self) -> Result<(), parse_rust_core::ParseError> {
55        use parse_rust_storage::StorageAdapter;
56        self.storage
57            .ensure_unique_index("_User", &["username"], None)
58            .await?;
59        self.storage
60            .ensure_unique_index("_User", &["email"], None)
61            .await?;
62        Ok(())
63    }
64
65    /// Resolve a session token to an ACL scope.
66    ///
67    /// **An unknown or expired token is an error, not anonymity.** Downgrading silently meant a
68    /// client whose session had gone continued to work as a public caller: writes succeeded
69    /// against public rows and reads returned public data, with no signal that authentication had
70    /// failed. Upstream answers `INVALID_SESSION_TOKEN` (209), and so does this.
71    pub fn scope_for_session(&self, token: &str) -> Result<AclScope, parse_rust_core::ParseError> {
72        match self.sessions.user_for(token) {
73            Some(object_id) => Ok(AclScope::User { object_id }),
74            None => Err(parse_rust_core::ParseError::new(
75                parse_rust_core::ErrorCode::InvalidSessionToken,
76                "Invalid session token",
77            )),
78        }
79    }
80}
81
82impl axum::extract::FromRef<AppState> for Arc<ServerConfig> {
83    fn from_ref(state: &AppState) -> Self {
84        state.config.clone()
85    }
86}