autoendpoint/routes/
registration.rs

1use actix_web::web::{Data, Json};
2use actix_web::{HttpRequest, HttpResponse};
3use cadence::{Histogrammed, StatsdClient};
4use uuid::Uuid;
5
6use crate::error::{ApiErrorKind, ApiResult};
7use crate::extractors::{
8    authorization_check::AuthorizationCheck, new_channel_data::NewChannelData,
9    registration_path_args::RegistrationPathArgs,
10    registration_path_args_with_uaid::RegistrationPathArgsWithUaid,
11    router_data_input::RouterDataInput, routers::Routers,
12};
13use crate::headers::util::get_header;
14use crate::server::AppState;
15
16use autopush_common::db::User;
17use autopush_common::endpoint::make_endpoint;
18use autopush_common::metric_name::MetricName;
19use autopush_common::metrics::StatsdClientExt;
20
21/// Handle the `POST /v1/{router_type}/{app_id}/registration` route
22pub async fn register_uaid_route(
23    path_args: RegistrationPathArgs,
24    router_data_input: RouterDataInput,
25    routers: Routers,
26    app_state: Data<AppState>,
27    request: HttpRequest,
28) -> ApiResult<HttpResponse> {
29    // Register with router
30    debug!(
31        "Registering a user with the {} router",
32        path_args.router_type
33    );
34    trace!("🌍 token = {}", router_data_input.token);
35    let router = routers.get(path_args.router_type);
36    let router_data = router.register(&router_data_input, &path_args.app_id)?;
37    incr_metric(MetricName::UaCommandRegister, &app_state.metrics, &request);
38
39    // Register user and channel in database
40    let user = User::builder()
41        .router_type(path_args.router_type.to_string())
42        .router_data(router_data)
43        .build()
44        .map_err(|e| ApiErrorKind::General(format!("User::builder error: {e}")))?;
45    let channel_id = router_data_input.channel_id.unwrap_or_else(Uuid::new_v4);
46    trace!("🌍 Creating user with UAID {}", user.uaid);
47    trace!("🌍 user = {:?}", user);
48    trace!("🌍 channel_id = {}", channel_id);
49    app_state.db.add_user(&user).await?;
50    app_state.db.add_channel(&user.uaid, &channel_id).await?;
51
52    // Make the endpoint URL
53    trace!("🌍 Creating endpoint for user");
54    let endpoint_url = make_endpoint(
55        &user.uaid,
56        &channel_id,
57        router_data_input.key.as_deref(),
58        app_state.settings.endpoint_url().as_str(),
59        &app_state.fernet,
60    )
61    .map_err(ApiErrorKind::EndpointUrl)?;
62    trace!("🌍 endpoint = {}", endpoint_url);
63
64    // Create the secret
65    trace!("🌍 Creating secret for UAID {}", user.uaid);
66    let auth_keys = app_state.settings.auth_keys();
67    let auth_key = auth_keys
68        .first()
69        .expect("At least one auth key must be provided in the settings");
70    let secret = AuthorizationCheck::generate_token(auth_key, &user.uaid)
71        .map_err(ApiErrorKind::RegistrationSecretHash)?;
72
73    trace!("🌍 Finished registering UAID {}", user.uaid);
74    Ok(HttpResponse::Ok().json(serde_json::json!({
75        "uaid": user.uaid,
76        "channelID": channel_id,
77        "endpoint": endpoint_url,
78        "secret": secret
79    })))
80}
81
82/// Handle the `DELETE /v1/{router_type}/{app_id}/registration/{uaid}` route
83pub async fn unregister_user_route(
84    _auth: AuthorizationCheck,
85    path_args: RegistrationPathArgsWithUaid,
86    app_state: Data<AppState>,
87) -> ApiResult<HttpResponse> {
88    let uaid = path_args.user.uaid;
89    debug!("🌍 Unregistering UAID {uaid}");
90    app_state.db.remove_user(&uaid).await?;
91    Ok(HttpResponse::Ok().finish())
92}
93
94/// Handle the `PUT /v1/{router_type}/{app_id}/registration/{uaid}` route
95pub async fn update_token_route(
96    _auth: AuthorizationCheck,
97    path_args: RegistrationPathArgsWithUaid,
98    router_data_input: RouterDataInput,
99    routers: Routers,
100    app_state: Data<AppState>,
101) -> ApiResult<HttpResponse> {
102    // Re-register with router
103    let RegistrationPathArgsWithUaid {
104        router_type,
105        app_id,
106        mut user,
107    } = path_args;
108    let uaid = user.uaid;
109    debug!("🌍 Updating the token of UAID {uaid} with the {router_type} router");
110    trace!("token = {}", router_data_input.token);
111    let router = routers.get(path_args.router_type);
112    let router_data = router.register(&router_data_input, &app_id)?;
113
114    // Update the user in the database
115    user.router_type = path_args.router_type.to_string();
116    user.router_data = Some(router_data);
117    trace!("🌍 Updating user with UAID {uaid}");
118    trace!("🌍 user = {user:?}");
119    if !app_state.db.update_user(&mut user).await? {
120        // Occurs occasionally on mobile records
121        return Err(ApiErrorKind::Conditional("update_user".to_owned()).into());
122    }
123
124    trace!("🌍 Finished updating token for UAID {uaid}");
125    Ok(HttpResponse::Ok().finish())
126}
127
128/// Handle the `POST /v1/{router_type}/{app_id}/registration/{uaid}/subscription` route
129pub async fn new_channel_route(
130    _auth: AuthorizationCheck,
131    path_args: RegistrationPathArgsWithUaid,
132    channel_data: Option<Json<NewChannelData>>,
133    app_state: Data<AppState>,
134) -> ApiResult<HttpResponse> {
135    // Add the channel
136    let uaid = path_args.user.uaid;
137    debug!("🌍 Adding a channel to UAID {uaid}");
138    let channel_data = channel_data.map(Json::into_inner).unwrap_or_default();
139    let channel_id = channel_data.channel_id.unwrap_or_else(Uuid::new_v4);
140    trace!("🌍 channel_id = {channel_id}");
141    app_state.db.add_channel(&uaid, &channel_id).await?;
142
143    // Make the endpoint URL
144    trace!("🌍 Creating endpoint for the new channel");
145    let endpoint_url = make_endpoint(
146        &uaid,
147        &channel_id,
148        channel_data.key.as_deref(),
149        app_state.settings.endpoint_url().as_str(),
150        &app_state.fernet,
151    )
152    .map_err(ApiErrorKind::EndpointUrl)?;
153    trace!("endpoint = {endpoint_url}");
154
155    Ok(HttpResponse::Ok().json(serde_json::json!({
156        "channelID": channel_id,
157        "endpoint": endpoint_url,
158    })))
159}
160
161/// Handle the `GET /v1/{router_type}/{app_id}/registration/{uaid}` route
162/// Since this is called daily by a mobile device, it can serve as part of
163/// a liveliness check for the device. This is more authoritative than
164/// relying on the bridge service to return a "success", since the bridge
165/// may retain "inactive" devices, but will immediately drop devices
166/// where Firefox has been uninstalled or that have been factory reset.
167pub async fn get_channels_route(
168    auth: AuthorizationCheck,
169    path_args: RegistrationPathArgsWithUaid,
170    app_state: Data<AppState>,
171) -> ApiResult<HttpResponse> {
172    let uaid = path_args.user.uaid;
173    let db = &app_state.db;
174    debug!("🌍 Getting channel IDs for UAID {uaid}");
175    //
176    if let Some(mut user) = db.get_user(&uaid).await? {
177        db.update_user(&mut user).await?;
178        // report the rough user agent so we know what clients are actively pinging us.
179        let os = auth.user_agent.metrics_os.clone();
180        let browser = auth.user_agent.metrics_browser.clone();
181        // use the "real" version here. (these are less normalized)
182        info!("Mobile client check";
183            "os" => auth.user_agent.os,
184            "os_version" => auth.user_agent.os_version,
185            "browser" => auth.user_agent.browser_name,
186            "browser_version" => auth.user_agent.browser_version);
187        // use the "metrics" version since we need to consider cardinality.
188        app_state
189            .metrics
190            .incr_with_tags(MetricName::UaConnectionCheck)
191            .with_tag("os", &os)
192            .with_tag("browser", &browser)
193            .send();
194    }
195    let channel_ids = db.get_channels(&uaid).await?;
196
197    app_state
198        .metrics
199        .histogram_with_tags(
200            MetricName::UaConnectionChannelCount.as_ref(),
201            channel_ids.len() as u64,
202        )
203        .with_tag_value("mobile")
204        .send();
205
206    Ok(HttpResponse::Ok().json(serde_json::json!({
207        "uaid": uaid,
208        "channelIDs": channel_ids
209    })))
210}
211
212/// Handle the `DELETE /v1/{router_type}/{app_id}/registration/{uaid}/subscription/{chid}` route
213pub async fn unregister_channel_route(
214    _auth: AuthorizationCheck,
215    path_args: RegistrationPathArgsWithUaid,
216    app_state: Data<AppState>,
217    request: HttpRequest,
218) -> ApiResult<HttpResponse> {
219    let channel_id = request
220        .match_info()
221        .get("chid")
222        .expect("{chid} must be part of the path")
223        .parse::<Uuid>()
224        .map_err(|_| ApiErrorKind::NoSubscription)?;
225    let uaid = path_args.user.uaid;
226    debug!("🌍 Unregistering CHID {channel_id} for UAID {uaid}");
227
228    incr_metric(
229        MetricName::UaCommandUnregister,
230        &app_state.metrics,
231        &request,
232    );
233    let channel_did_exist = app_state.db.remove_channel(&uaid, &channel_id).await?;
234
235    if channel_did_exist {
236        Ok(HttpResponse::Ok().finish())
237    } else {
238        debug!("Channel did not exist");
239        Err(ApiErrorKind::NoSubscription.into())
240    }
241}
242
243/// Increment a metric with data from the request
244fn incr_metric(metric: MetricName, metrics: &StatsdClient, request: &HttpRequest) {
245    metrics
246        .incr_with_tags(metric)
247        .with_tag(
248            "user_agent",
249            get_header(request, "User-Agent").unwrap_or("unknown"),
250        )
251        .with_tag("host", get_header(request, "Host").unwrap_or("unknown"))
252        .send()
253}