mirror of
https://git.ethanthesleepy.one/ethanaobrien/ew
synced 2026-08-27 15:32:17 +08:00
Fix some things with the new feature
This commit is contained in:
@@ -302,34 +302,27 @@ async fn session(Body(body): Body) -> impl Responder {
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Point a card at an existing (phone) account. The proof is the data-transfer
|
// Point a card at a player account the caller has already identified. This is
|
||||||
// code and its password - the same check /api/user/gglverifymigrationcode makes
|
// the rule every entrance shares - the card id is validated, a cabinet's own
|
||||||
// (user.rs:214-220), through the one function that owns that comparison.
|
// identities are refused, the mapping is replaced and the throwaway account
|
||||||
//
|
// the card was carrying is cleaned up - and the proof of *whose* account it is
|
||||||
// Shared by the cabinet's own /api/arcade/bind and the webui account page's
|
// belongs to the caller: the cabinet's /api/arcade/bind takes the game's
|
||||||
// form: the browser cannot speak the game protocol (encrypted bodies behind the
|
// data-transfer code and password (bind_card), the webui account page takes
|
||||||
// asset gate), so it gets its own entrance, not its own copy of the rule.
|
// the signed-in session itself, which already proves the account.
|
||||||
pub fn bind_card(card: &str, migration_code: &str, pass: &str) -> Result<i64, String> {
|
pub fn bind_card_to(card: &str, user_id: i64) -> Result<i64, String> {
|
||||||
if disabled() {
|
if disabled() {
|
||||||
return Err(String::from("Arcade mode is disabled on this server"));
|
return Err(String::from("Arcade mode is disabled on this server"));
|
||||||
}
|
}
|
||||||
let Some(card) = card_id(&object!{ "card_id": card }) else {
|
let Some(card) = card_id(&object!{ "card_id": card }) else {
|
||||||
return Err(String::from("That is not a usable card id"));
|
return Err(String::from("That is not a usable card id"));
|
||||||
};
|
};
|
||||||
|
|
||||||
let account = userdata::user::migration::get_acc_transfer(migration_code, pass);
|
|
||||||
if !account["success"].as_bool().unwrap_or(false) || account["user_id"] == 0 {
|
|
||||||
return Err(String::from("Transfer code and password don't match"));
|
|
||||||
}
|
|
||||||
let Some(user_id) = account["user_id"].as_i64() else {
|
|
||||||
return Err(String::from("Transfer code and password don't match"));
|
|
||||||
};
|
|
||||||
// A cabinet's own two identities are not player accounts and may never be
|
// A cabinet's own two identities are not player accounts and may never be
|
||||||
// behind a card. The guest in particular is rewritten for a stranger every
|
// behind a card. The guest in particular is rewritten for a stranger every
|
||||||
// credit: a card pointing at it would outlive that reset, and /session hands
|
// credit: a card pointing at it would outlive that reset, and /session hands
|
||||||
// out the account's current login token to whoever presents the card. The
|
// out the account's current login token to whoever presents the card. Every
|
||||||
// proof this bind takes - a transfer code and password - is exactly what a
|
// proof a bind can take - a transfer code and password, a webui login - is
|
||||||
// hostile client can register on a guest during its own credit.
|
// exactly what a hostile client can register on a guest during its own
|
||||||
|
// credit, so the refusal lives here, below all of them.
|
||||||
if database::machine_of_account(user_id).is_some() {
|
if database::machine_of_account(user_id).is_some() {
|
||||||
return Err(String::from("That account belongs to an arcade cabinet"));
|
return Err(String::from("That account belongs to an arcade cabinet"));
|
||||||
}
|
}
|
||||||
@@ -351,6 +344,27 @@ pub fn bind_card(card: &str, migration_code: &str, pass: &str) -> Result<i64, St
|
|||||||
Ok(user_id)
|
Ok(user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The cabinet's bind: the proof that a phone account is the player's is the
|
||||||
|
// game's own data-transfer code and password. On a phone the transfer runs
|
||||||
|
// through GREE's native code, and ew's /api/user/gglverifymigrationcode is only
|
||||||
|
// the desktop route with GGL off; what both share is the `migration` table -
|
||||||
|
// the code and the hashed password - which get_acc_transfer is the one owner
|
||||||
|
// of. A cabinet is a Windows build with GGL off, so that pair is its legitimate
|
||||||
|
// path, typed in from the game's Data Transfer screen.
|
||||||
|
pub fn bind_card(card: &str, migration_code: &str, pass: &str) -> Result<i64, String> {
|
||||||
|
if disabled() {
|
||||||
|
return Err(String::from("Arcade mode is disabled on this server"));
|
||||||
|
}
|
||||||
|
let account = userdata::user::migration::get_acc_transfer(migration_code, pass);
|
||||||
|
if !account["success"].as_bool().unwrap_or(false) || account["user_id"] == 0 {
|
||||||
|
return Err(String::from("Transfer code and password don't match"));
|
||||||
|
}
|
||||||
|
let Some(user_id) = account["user_id"].as_i64() else {
|
||||||
|
return Err(String::from("Transfer code and password don't match"));
|
||||||
|
};
|
||||||
|
bind_card_to(card, user_id)
|
||||||
|
}
|
||||||
|
|
||||||
async fn bind(Body(body): Body) -> impl Responder {
|
async fn bind(Body(body): Body) -> impl Responder {
|
||||||
match bind_card(
|
match bind_card(
|
||||||
body["card_id"].as_str().unwrap_or(""),
|
body["card_id"].as_str().unwrap_or(""),
|
||||||
@@ -717,6 +731,51 @@ mod tests {
|
|||||||
crate::database::arcade::delete_machine(&machine_id);
|
crate::database::arcade::delete_machine(&machine_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// bind_card_to is the rule under both entrances - the cabinet's transfer
|
||||||
|
// proof and the webui's session - so whatever named the account, a cabinet's
|
||||||
|
// own identities are refused, and a provable throwaway goes with its card
|
||||||
|
#[test]
|
||||||
|
fn bind_card_to_refuses_cabinet_identities_and_takes_the_throwaway_with_the_card() {
|
||||||
|
let _lock = crate::runtime::lock_test_data_path();
|
||||||
|
use crate::database::arcade as db;
|
||||||
|
|
||||||
|
let (machine_account, _) = userdata::starter::create("Cabinet Bind").unwrap();
|
||||||
|
let (guest_account, _) = userdata::starter::create(GUEST_NAME).unwrap();
|
||||||
|
let machine_id = db::generate_machine_id();
|
||||||
|
db::insert_machine(&machine_id, "Cabinet Bind", machine_account, guest_account);
|
||||||
|
let (player, _) = userdata::starter::create("Player").unwrap();
|
||||||
|
|
||||||
|
// The id is validated, not repaired
|
||||||
|
assert!(bind_card_to("0123-4567", player).is_err());
|
||||||
|
assert!(db::card_user("0123-4567").is_none());
|
||||||
|
|
||||||
|
// Neither cabinet identity may sit behind a card
|
||||||
|
let card = "4242424242424242";
|
||||||
|
assert!(bind_card_to(card, machine_account).is_err());
|
||||||
|
assert!(bind_card_to(card, guest_account).is_err());
|
||||||
|
assert!(db::card_user(card).is_none());
|
||||||
|
|
||||||
|
// A throwaway the card was carrying leaves with it...
|
||||||
|
let (orphan, _) = userdata::starter::create("2424").unwrap();
|
||||||
|
db::set_card(card, orphan);
|
||||||
|
assert_eq!(bind_card_to(card, player), Ok(player));
|
||||||
|
assert_eq!(db::card_user(card), Some(player));
|
||||||
|
assert!(userdata::get_acc_from_uid(orphan)["error"].as_bool().unwrap_or(false), "the throwaway survived");
|
||||||
|
|
||||||
|
// ...but an account somebody played on stays
|
||||||
|
let (played, played_token) = userdata::starter::create("2425").unwrap();
|
||||||
|
let mut user = userdata::get_acc_from_uid(played);
|
||||||
|
user["live_list"].push(object!{ master_live_id: 1100101, level: 4, clear_count: 1, high_score: 1, max_combo: 1 }).unwrap();
|
||||||
|
userdata::save_acc(&played_token, user);
|
||||||
|
let other = "2525252525252525";
|
||||||
|
db::set_card(other, played);
|
||||||
|
assert_eq!(bind_card_to(other, player), Ok(player));
|
||||||
|
assert_eq!(db::card_user(other), Some(player));
|
||||||
|
assert!(!userdata::get_acc_from_uid(played)["error"].as_bool().unwrap_or(false), "a played-on account followed its card");
|
||||||
|
|
||||||
|
db::delete_machine(&machine_id);
|
||||||
|
}
|
||||||
|
|
||||||
// A cabinet's song that ended with an empty life gauge is reported at
|
// A cabinet's song that ended with an empty life gauge is reported at
|
||||||
// /live/retire, which carries no arcade flag - the start record is what
|
// /live/retire, which carries no arcade flag - the start record is what
|
||||||
// proves it was a cabinet's - and it lands in the ledger as a failed song
|
// proves it was a cabinet's - and it lands in the ledger as a failed song
|
||||||
|
|||||||
@@ -607,24 +607,22 @@ pub fn remove_arcade_machine(req: HttpRequest, body: String) -> HttpResponse {
|
|||||||
.body(jzon::stringify(resp))
|
.body(jzon::stringify(resp))
|
||||||
}
|
}
|
||||||
|
|
||||||
// The account page's "bind arcade card" form. The cabinet's own endpoint speaks
|
// The account page's "bind arcade card" form: the card id, and nothing else.
|
||||||
// the encrypted game protocol behind the asset gate, which a browser cannot, so
|
// The webui session already proves whose account this is, so the card is bound
|
||||||
// this is the browser's door onto the same rule - arcade::bind_card, not a copy
|
// to the signed-in account through arcade::bind_card_to - the same rule the
|
||||||
// of it. A webui session is required on top of the transfer code and password:
|
// cabinet's /api/arcade/bind applies once its own proof, the transfer code and
|
||||||
// the page is behind login anyway, and the extra proof costs nothing.
|
// password, has named the account. The cabinet endpoint speaks the encrypted
|
||||||
|
// game protocol behind the asset gate, which a browser cannot, so this is the
|
||||||
|
// browser's door onto that rule rather than a copy of it.
|
||||||
pub fn bind_arcade_card(req: HttpRequest, body: String) -> HttpResponse {
|
pub fn bind_arcade_card(req: HttpRequest, body: String) -> HttpResponse {
|
||||||
if crate::router::arcade::disabled() {
|
if crate::router::arcade::disabled() {
|
||||||
return HttpResponse::NotFound().finish();
|
return HttpResponse::NotFound().finish();
|
||||||
}
|
}
|
||||||
if session_uid(&req).is_none() {
|
let Some(uid) = session_uid(&req) else {
|
||||||
return error("Not logged in");
|
return error("Not logged in");
|
||||||
}
|
};
|
||||||
let body = jzon::parse(&body).unwrap_or(object!{});
|
let body = jzon::parse(&body).unwrap_or(object!{});
|
||||||
match crate::router::arcade::bind_card(
|
match crate::router::arcade::bind_card_to(body["card_id"].as_str().unwrap_or(""), uid) {
|
||||||
body["card_id"].as_str().unwrap_or(""),
|
|
||||||
body["migrationCode"].as_str().unwrap_or(""),
|
|
||||||
body["pass"].as_str().unwrap_or("")
|
|
||||||
) {
|
|
||||||
Ok(user_id) => {
|
Ok(user_id) => {
|
||||||
let resp = object!{
|
let resp = object!{
|
||||||
result: "OK",
|
result: "OK",
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ pub struct HostConfig {
|
|||||||
pub enable_custom_songs: bool,
|
pub enable_custom_songs: bool,
|
||||||
pub enable_custom_cards: bool,
|
pub enable_custom_cards: bool,
|
||||||
pub enable_custom_3dmv: bool,
|
pub enable_custom_3dmv: bool,
|
||||||
|
pub enable_arcade: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lets an embedding app (or the tests) enable the opt-in custom songs feature
|
// Lets an embedding app (or the tests) enable the opt-in custom songs feature
|
||||||
@@ -42,6 +43,10 @@ pub fn set_enable_custom_3dmv(enabled: bool) {
|
|||||||
HOST_CONFIG.write().unwrap().enable_custom_3dmv = enabled;
|
HOST_CONFIG.write().unwrap().enable_custom_3dmv = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_enable_arcade(enabled: bool) {
|
||||||
|
HOST_CONFIG.write().unwrap().enable_arcade = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
// The --owner uids: the permission system's bootstrap grantors. Process-level
|
// The --owner uids: the permission system's bootstrap grantors. Process-level
|
||||||
// state rather than db rows so they work on a fresh install and can't be
|
// state rather than db rows so they work on a fresh install and can't be
|
||||||
// revoked through the webui
|
// revoked through the webui
|
||||||
@@ -178,6 +183,9 @@ pub fn overlay_args(args: &mut crate::options::Args) {
|
|||||||
if cfg.enable_custom_3dmv {
|
if cfg.enable_custom_3dmv {
|
||||||
args.enable_custom_3dmv = true;
|
args.enable_custom_3dmv = true;
|
||||||
}
|
}
|
||||||
|
if cfg.enable_arcade {
|
||||||
|
args.enable_arcade = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// idk why an ai put tests here but they are here now. Yay tests????
|
// idk why an ai put tests here but they are here now. Yay tests????
|
||||||
@@ -201,5 +209,6 @@ pub fn lock_test_data_path() -> std::sync::MutexGuard<'static, ()> {
|
|||||||
set_enable_custom_songs(true);
|
set_enable_custom_songs(true);
|
||||||
set_enable_custom_cards(true);
|
set_enable_custom_cards(true);
|
||||||
set_enable_custom_3dmv(true);
|
set_enable_custom_3dmv(true);
|
||||||
|
set_enable_arcade(true);
|
||||||
guard
|
guard
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user