E-17: fcm_token at most 512 characters (security L-4)
An authenticated device could store a token of up to the 1 MiB body limit, sent to FCM in every push for it. Now (proposed D-114) more than 512 characters after trimming is 400 invalid_request with field fcm_token, like the empty string (D-034); E-17's only documented error stays the only one. Test at 512 and 513. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
@@ -14,6 +14,9 @@ use crate::error::{AppError, ErrorCode};
|
|||||||
use crate::openapi::responses::{BadRequest, Conflict, Forbidden, NotFound};
|
use crate::openapi::responses::{BadRequest, Conflict, Forbidden, NotFound};
|
||||||
use crate::wire::{DeviceList, PushTokenRequest, Role, UuidSchema};
|
use crate::wire::{DeviceList, PushTokenRequest, Role, UuidSchema};
|
||||||
|
|
||||||
|
/// Longest `fcm_token` stored, in characters after trimming (D-114; a Firebase Installation ID has 22).
|
||||||
|
pub const FCM_TOKEN_MAX: usize = 512;
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/devices",
|
path = "/devices",
|
||||||
@@ -104,6 +107,12 @@ pub async fn put_push_token(
|
|||||||
.with_field("fcm_token"),
|
.with_field("fcm_token"),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Some(long) if long.chars().count() > FCM_TOKEN_MAX => {
|
||||||
|
return Err(AppError::invalid_request(format!(
|
||||||
|
"fcm_token must be at most {FCM_TOKEN_MAX} characters"
|
||||||
|
))
|
||||||
|
.with_field("fcm_token"));
|
||||||
|
}
|
||||||
other => other.map(str::to_owned),
|
other => other.map(str::to_owned),
|
||||||
};
|
};
|
||||||
let mut tx = db::begin_immediate(&state.db).await?;
|
let mut tx = db::begin_immediate(&state.db).await?;
|
||||||
|
|||||||
@@ -177,3 +177,26 @@ async fn e17_invalid_request() {
|
|||||||
assert_error(&reply, 400, "invalid_request");
|
assert_error(&reply, 400, "invalid_request");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn e17_fcm_token_is_at_most_512_characters() {
|
||||||
|
// D-114: 512 characters after trimming are stored, 513 are invalid_request with the field named.
|
||||||
|
let app = TestApp::household().await;
|
||||||
|
let put = |token: String| {
|
||||||
|
app.call(
|
||||||
|
Method::PUT,
|
||||||
|
"/devices/current/push-token",
|
||||||
|
&admin_token(),
|
||||||
|
Some(json!({ "fcm_token": token })),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
assert_eq!(put(format!(" {} ", "å".repeat(512))).await.status, 204);
|
||||||
|
let reply = put("f".repeat(513)).await;
|
||||||
|
assert_field_error(&reply, 400, "invalid_request", "fcm_token");
|
||||||
|
let stored: Option<String> = sqlx::query_scalar("SELECT fcm_token FROM devices WHERE id = ?")
|
||||||
|
.bind(ADMIN_DEVICE_ID)
|
||||||
|
.fetch_one(&app.state.db)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(stored, Some("å".repeat(512)));
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user