diff --git a/server/crates/kvitt-server/src/api/devices.rs b/server/crates/kvitt-server/src/api/devices.rs index e1b4025..30cf515 100644 --- a/server/crates/kvitt-server/src/api/devices.rs +++ b/server/crates/kvitt-server/src/api/devices.rs @@ -14,6 +14,9 @@ use crate::error::{AppError, ErrorCode}; use crate::openapi::responses::{BadRequest, Conflict, Forbidden, NotFound}; 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( get, path = "/devices", @@ -104,6 +107,12 @@ pub async fn put_push_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), }; let mut tx = db::begin_immediate(&state.db).await?; diff --git a/server/crates/kvitt-server/tests/core_api/devices.rs b/server/crates/kvitt-server/tests/core_api/devices.rs index d2310b2..d35a3a5 100644 --- a/server/crates/kvitt-server/tests/core_api/devices.rs +++ b/server/crates/kvitt-server/tests/core_api/devices.rs @@ -177,3 +177,26 @@ async fn e17_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 = 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))); +}