feat: mod platform integration (showcase, cloud slots, cloud screen, server-side unlink)

Mod:
- %config slots/pull/publish/unpublish for the four cloud slots
- %showcase [new|popular] [page] | load | copy, with number references
- %cloud opens a CloudScreen (link/unlink, slots, showcase) on vanilla widgets
- %config load IDDQD works offline (everything off except ChinaHat)
- default backend URL is now the production gateway
- shared ConfigRemoteApplier and ClientThread replace per-class copies
- %link unlink revokes the link on the server, then clears the local token

Backend:
- POST /device/revoke (RFC 7009 style self-revoke, always 204), rate limited
  to 10/min per IP at the gateway

CloudScreen is compiled but has not been opened in a running client yet.
This commit is contained in:
loki5512344 2026-10-02 09:23:44 +02:00
parent e00ea8eb11
commit 44353e893c
Signed by: boba
GPG key ID: 253067914055423B
24 changed files with 894 additions and 52 deletions

View file

@ -114,3 +114,36 @@ async fn unknown_device_token_does_not_authenticate() {
.unwrap();
assert_eq!(r, None);
}
#[tokio::test]
async fn self_revoke_by_token_deletes_only_that_link() {
let pool = common::test_pool().await;
let server = common::test_server(accounts_service::build_app(
pool.clone(),
&common::test_config(),
));
let (account, _) = common::register_account(&server).await;
let t1 = link_device(&server, account).await;
let t2 = link_device(&server, account).await;
server
.post("/device/revoke")
.json(&serde_json::json!({ "device_token": t1 }))
.await
.assert_status(StatusCode::NO_CONTENT);
// Unknown tokens answer the same way: no oracle.
server
.post("/device/revoke")
.json(&serde_json::json!({ "device_token": "lvd_nope" }))
.await
.assert_status(StatusCode::NO_CONTENT);
let gone = accounts_service::device::links::authenticate(&pool, &t1)
.await
.unwrap();
let kept = accounts_service::device::links::authenticate(&pool, &t2)
.await
.unwrap();
assert_eq!(gone, None);
assert_eq!(kept, Some(account));
}