Skip to main content

didbot_serve/routes/
preferences.rs

1use super::*;
2
3/// Where `getPreferences` is served, which a DPoP proof binds to.
4const GET_PREFERENCES_PATH: &str = "/xrpc/app.bsky.actor.getPreferences";
5
6/// Where `putPreferences` is served.
7const PUT_PREFERENCES_PATH: &str = "/xrpc/app.bsky.actor.putPreferences";
8
9/// `GET /xrpc/app.bsky.actor.getPreferences`
10///
11/// The account's app preferences. This server stores none, so every account
12/// has none, and the answer is an empty list. Takes the credentials
13/// `getSession` takes. Served here and never proxied, as the reference PDS
14/// serves it: preferences are private to the account.
15pub(crate) async fn get_preferences(State(state): State<AppState>, headers: HeaderMap) -> Response {
16    match signed_in(&state, &headers, "GET", GET_PREFERENCES_PATH) {
17        Ok(did) => {
18            debug!(did, "answered getPreferences with none stored");
19            Json(json!({ "preferences": [] })).into_response()
20        }
21        Err(err) => err.into_response(),
22    }
23}
24
25/// `POST /xrpc/app.bsky.actor.putPreferences`
26///
27/// Refused with `MethodNotImplemented`, which the XRPC spec defines as a
28/// known method this server does not implement, after the caller signs
29/// in. Served here so the preferences in the body are never proxied to
30/// another service.
31pub(crate) async fn put_preferences(State(state): State<AppState>, headers: HeaderMap) -> Response {
32    match signed_in(&state, &headers, "POST", PUT_PREFERENCES_PATH) {
33        Ok(did) => {
34            info!(
35                did,
36                "refused putPreferences: this server stores no preferences"
37            );
38            ApiError::new(
39                StatusCode::NOT_IMPLEMENTED,
40                "MethodNotImplemented",
41                "this server stores no preferences, so app.bsky.actor.putPreferences is not \
42                 implemented; app.bsky.actor.getPreferences answers an empty list",
43            )
44            .into_response()
45        }
46        Err(err) => err.into_response(),
47    }
48}
49
50/// The account the request's credential signs in as, checked the way
51/// `getSession` checks it.
52fn signed_in(
53    state: &AppState,
54    headers: &HeaderMap,
55    method: &str,
56    path: &str,
57) -> Result<String, ApiError> {
58    let nsid = path.trim_start_matches("/xrpc/");
59    require_lifecycle(state, |p| p.serves_reads, nsid)?;
60    state.estop.check_use(EstopRefusal::Operation)?;
61    let writer = auth::require_agent_token_or_dpop(
62        state.registry.as_ref(),
63        &state.oauth,
64        headers,
65        method,
66        path,
67    )?;
68    Ok(writer.did().to_owned())
69}