fix(website): resolve sign-in by the asset that signed, not only the BTC hash

website_verify resolved every asset through get_user_by_pubkey_hash, but
users.pubkey_hash only ever holds the BTC key's hash. On this backend only BTC
could ever resolve; ltc, xmr, wow and grin were structurally impossible, and the
failure was indistinguishable from an unregistered user, so the page told people
to register with an extension they already had.

That did not surface while smirk.cash pointed at the legacy backend, which
resolves by asset key against its own database. It becomes the whole bug the
moment the site is repointed here, so it has to land with that change, not after.

get_user_by_pubkey_hash stays as the BTC fallback: of 95 users on this backend,
84 arrived through migrate-legacy, which imported identity without keys, and
would otherwise stop resolving entirely.

The rejection message is unchanged and still says nothing about whether an
account exists.
This commit is contained in:
2026-09-14 22:07:20 -04:00
parent 1d7e1d5a78
commit 985a05adf8
12 changed files with 724 additions and 27 deletions
+24
View File
@@ -98,6 +98,7 @@ pub async fn try_app_with(mutate: impl FnOnce(&mut Config)) -> Option<TestApp> {
relay,
web_challenges: Arc::default(),
prices,
stats_cache: Arc::default(),
admin_sessions,
shutdown: Arc::default(),
});
@@ -247,6 +248,29 @@ impl TestApp {
user.id
}
/// Create a user holding a claimed handle. Returns the handle so a test can
/// assert it never appears on a public surface.
pub async fn create_user_with_handle(&self) -> String {
use smirk_backend_core::models::db::NewUser;
// Shaped like a real handle (lowercase, <= 32 chars) but unmistakable in
// a response body, so a leak is a match and not a coincidence.
let handle = format!("h{}", &Uuid::new_v4().simple().to_string()[..16]);
self.state
.db
.create_user(NewUser {
username: Some(handle.clone()),
pubkey_hash: Some(format!("pk-{}", Uuid::new_v4())),
nostr_pubkey: None,
wallet_birthday: None,
seed_fingerprint: None,
xmr_start_height: None,
wow_start_height: None,
})
.await
.expect("create user with handle");
handle
}
/// Create a user + an active session, returning `(user_id, access, refresh)`.
/// Bypasses the signed-registration handshake so session-gated routes can be
/// exercised without forging a BIP-137 signature.