parse_rust_server/state.rs
1//! Server state: config plus the storage adapter.
2//!
3//! Concrete over `MongoAdapter` rather than generic or boxed. 0.2.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_core::ParseError;
11use parse_rust_mongo::MongoAdapter;
12
13use crate::auth::Authority;
14use crate::config::ServerConfig;
15use crate::request::RequestContext;
16
17#[derive(Clone)]
18pub struct AppState {
19 config: Arc<ServerConfig>,
20 storage: Arc<MongoAdapter>,
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 }
29 }
30
31 pub fn config(&self) -> &ServerConfig {
32 &self.config
33 }
34
35 pub fn storage(&self) -> &MongoAdapter {
36 &self.storage
37 }
38
39 /// Create the indexes parse-server creates at boot.
40 ///
41 /// **The names are contract, not housekeeping.** Both adapters recover `duplicated_field` by
42 /// regex over the index name, and the Mongo regex matches only auto-generated `<field>_1`
43 /// names, so passing `None` here (which lets the driver auto-name) is what makes a username
44 /// collision surface as 202 `USERNAME_TAKEN` rather than a bare 137. Naming them ourselves
45 /// would silently change the error a client sees.
46 ///
47 /// Upstream gates each of these behind a `databaseOptions.createIndex*` flag
48 /// (`DatabaseController.js:1981-2038`). Only `createIndexRoleName` is modeled; the two
49 /// `_User` indexes are unconditional here, which is what their flags default to.
50 pub async fn ensure_indexes(&self) -> Result<(), ParseError> {
51 use parse_rust_storage::StorageAdapter;
52 self.storage
53 .ensure_index("_User", &["username"], None, true, false)
54 .await?;
55 self.storage
56 .ensure_index("_User", &["email"], None, true, false)
57 .await?;
58 // **The case-insensitive pair, and note they are not unique**
59 // (`DatabaseController.js:1988-2005`). Upstream's `ensureIndex` never sets `unique`, so
60 // these exist to make the collated uniqueness *query* fast, not to enforce anything. The
61 // enforcement is the query in `validate_user_identity`.
62 //
63 // Creating them unique looks stricter and is a mixed-fleet break: parse-server booting
64 // against the same database asks for the non-unique form under the same name, gets
65 // `IndexKeySpecsConflict` (86), and refuses to start. Gate D found exactly that.
66 //
67 // Named rather than auto-named, because upstream names them and a mixed fleet has to agree
68 // on what exists.
69 self.storage
70 .ensure_index(
71 "_User",
72 &["username"],
73 Some("case_insensitive_username"),
74 false,
75 true,
76 )
77 .await?;
78 self.storage
79 .ensure_index(
80 "_User",
81 &["email"],
82 Some("case_insensitive_email"),
83 false,
84 true,
85 )
86 .await?;
87 // `_Role.name`, `ensureUniqueness('_Role', requiredRoleFields, ['name'])`
88 // (`DatabaseController.js:2033-2038`). Upstream passes no index name, so Mongo
89 // auto-generates `name_1`, which is the form the `duplicated_field` regex matches.
90 //
91 // Without it two `_Role` rows can share a name, and an ACL entry of `role:X` then grants
92 // every member of both. That is a privilege-escalation path, not a data-hygiene one.
93 if self.config.create_index_role_name {
94 self.storage
95 .ensure_index("_Role", &["name"], None, true, false)
96 .await?;
97 }
98 Ok(())
99 }
100
101 /// Resolve the request context: session, roles, ACL scope and the schema snapshot.
102 ///
103 /// Called **once** per HTTP request, including a `/batch` whose sub-requests then share it.
104 pub async fn request_context(
105 &self,
106 authority: &Authority,
107 ) -> Result<RequestContext, ParseError> {
108 crate::request::resolve(&self.storage, &self.config, authority).await
109 }
110}
111
112impl axum::extract::FromRef<AppState> for Arc<ServerConfig> {
113 fn from_ref(state: &AppState) -> Self {
114 state.config.clone()
115 }
116}