Skip to main content

parse_rust_server/routes/
features.rs

1//! `GET /serverInfo`. Master-key gated.
2//!
3//! Upstream: `src/Routers/FeaturesRouter.js`. The `features` object is a static capability
4//! advertisement with three values read from config. Parse Dashboard reads it, so the key set
5//! and nesting are wire contract, not documentation.
6
7use std::sync::Arc;
8
9use axum::extract::State;
10use axum::response::{IntoResponse, Response};
11use serde_json::json;
12
13use crate::auth::Authority;
14use crate::config::{ServerConfig, REPORTED_PARSE_SERVER_VERSION};
15use crate::response::HttpError;
16
17pub async fn server_info(
18    State(config): State<Arc<ServerConfig>>,
19    authority: Authority,
20) -> Response {
21    // `promiseEnforceMasterKeyAccess`: master only. Maintenance does not satisfy this, because
22    // upstream checks `request.auth.isMaster`, and a maintenance request is not master.
23    if !authority.is_master() {
24        return HttpError::master_key_required(config.enable_sanitized_error_response)
25            .into_response();
26    }
27
28    // The key set and nesting are transcribed from FeaturesRouter and are wire contract. The
29    // values are **not** transcribed: upstream hardcodes almost all of them to `true` because
30    // upstream implements them, and repeating that here would advertise a schema API, hooks, a
31    // global config, cloud jobs, a log API and push, none of which have routes. Parse Dashboard
32    // renders its UI from this object, so a copied `true` becomes a control that 404s when a user
33    // clicks it. See `FeatureSupport` for the reasoning; each flag flips as its subsystem lands.
34    let f = &config.features;
35    let features = json!({
36        "globalConfig": {
37            "create": f.global_config, "read": f.global_config,
38            "update": f.global_config, "delete": f.global_config,
39        },
40        "hooks": {
41            "create": f.hooks, "read": f.hooks, "update": f.hooks, "delete": f.hooks,
42        },
43        "cloudCode": { "jobs": f.cloud_code_jobs },
44        "logs": {
45            "level": f.logs, "size": f.logs, "order": f.logs, "until": f.logs, "from": f.logs,
46        },
47        "push": {
48            "immediatePush": config.has_push_support,
49            "scheduledPush": config.has_push_scheduled_support,
50            "storedPushData": config.has_push_support,
51            "pushAudiences": f.push_audiences,
52            "localization": f.push_audiences,
53        },
54        "schemas": {
55            "addField": f.schemas,
56            "removeField": f.schemas,
57            "addClass": f.schemas,
58            "removeClass": f.schemas,
59            "clearAllDataFromClass": f.schemas,
60            // Upstream ships this as `false` even with the schema API fully present, so it stays
61            // false here for both reasons and never follows `f.schemas`.
62            "exportClass": false,
63            "editClassLevelPermissions": f.schemas,
64            "editPointerPermissions": f.schemas,
65        },
66        "settings": { "securityCheck": config.security_check_enabled },
67    });
68
69    axum::Json(json!({
70        "features": features,
71        "parseServerVersion": REPORTED_PARSE_SERVER_VERSION,
72    }))
73    .into_response()
74}