Skip to content

Commit

Permalink
Store unique unblinded message
Browse files Browse the repository at this point in the history
  • Loading branch information
TonyGiorgio committed Mar 7, 2024
1 parent b903adb commit 70e849b
Show file tree
Hide file tree
Showing 8 changed files with 111 additions and 5 deletions.
1 change: 1 addition & 0 deletions migrations/2024-02-20-210617_user_info/up.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ CREATE TABLE app_user (
id SERIAL PRIMARY KEY,
pubkey VARCHAR(64) NOT NULL,
name VARCHAR(255) NOT NULL UNIQUE,
unblinded_msg VARCHAR(255) NOT NULL UNIQUE,
federation_id VARCHAR(64) NOT NULL,
federation_invite_code VARCHAR(255) NOT NULL
);
Expand Down
6 changes: 6 additions & 0 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::models::{
#[cfg_attr(test, automock)]
pub(crate) trait DBConnection {
fn check_name_available(&self, name: String) -> anyhow::Result<bool>;
fn check_token_not_spent(&self, msg: String) -> anyhow::Result<bool>;
fn insert_new_user(&self, name: NewAppUser) -> anyhow::Result<AppUser>;
fn get_pending_invoices(&self) -> anyhow::Result<Vec<Invoice>>;
fn insert_new_invoice(&self, invoice: NewInvoice) -> anyhow::Result<Invoice>;
Expand All @@ -35,6 +36,11 @@ impl DBConnection for PostgresConnection {
AppUser::check_available_name(conn, name)
}

fn check_token_not_spent(&self, msg: String) -> anyhow::Result<bool> {
let conn = &mut self.db.get()?;
AppUser::check_token_not_spent(conn, msg)
}

fn insert_new_user(&self, new_user: NewAppUser) -> anyhow::Result<AppUser> {
let conn = &mut self.db.get()?;
new_user.insert(conn)
Expand Down
1 change: 1 addition & 0 deletions src/lnurlp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ mod tests_integration {
pubkey: "e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443".to_string(),
name: username.clone(),
federation_id: "".to_string(),
unblinded_msg: "".to_string(),
federation_invite_code: "".to_string(),
};

Expand Down
10 changes: 10 additions & 0 deletions src/models/app_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ pub struct AppUser {
pub id: i32,
pub pubkey: String,
pub name: String,
pub unblinded_msg: String,
pub federation_id: String,
pub federation_invite_code: String,
}
Expand Down Expand Up @@ -42,6 +43,14 @@ impl AppUser {
== 0)
}

pub fn check_token_not_spent(conn: &mut PgConnection, msg: String) -> anyhow::Result<bool> {
Ok(app_user::table
.filter(app_user::unblinded_msg.eq(msg))
.count()
.get_result::<i64>(conn)?
== 0)
}

pub fn get_by_pubkey(
conn: &mut PgConnection,
pubkey: String,
Expand All @@ -59,6 +68,7 @@ pub struct NewAppUser {
pub pubkey: String,
pub name: String,
pub federation_id: String,
pub unblinded_msg: String,
pub federation_invite_code: String,
}

Expand Down
2 changes: 2 additions & 0 deletions src/models/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ diesel::table! {
pubkey -> Varchar,
#[max_length = 255]
name -> Varchar,
#[max_length = 255]
unblinded_msg -> Varchar,
#[max_length = 64]
federation_id -> Varchar,
#[max_length = 255]
Expand Down
1 change: 1 addition & 0 deletions src/nostr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ mod tests_integration {
pubkey: "e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443".to_string(),
name: username.clone(),
federation_id: "".to_string(),
unblinded_msg: "".to_string(),
federation_invite_code: "".to_string(),
};

Expand Down
94 changes: 89 additions & 5 deletions src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,20 @@ pub async fn register(
return Err((StatusCode::BAD_REQUEST, "Unavailable".to_string()));
}

// verify token and double check that it has not been spent before
if !req.verify(state.auth_pk) {
return Err((StatusCode::UNAUTHORIZED, "Invalid blind sig".to_string()));
}
match state.db.check_token_not_spent(req.msg.0.to_string()) {
Ok(true) => (),
Ok(false) => {
return Err((StatusCode::BAD_REQUEST, "Already Registered".to_string()));
}
Err(e) => {
error!("Error in register: {e:?}");
return Err((StatusCode::INTERNAL_SERVER_ERROR, "ServerError".to_string()));
}
}

match state.db.check_name_available(req.name.clone()) {
Ok(true) => (),
Expand Down Expand Up @@ -70,9 +81,6 @@ pub async fn register(
}
}

// TODO insert blinding info and new user as an atomic transaction
// TODO save nonce to db and check for replay attacks

match state.db.insert_new_user(req.into()) {
Ok(_) => Ok(RegisterResponse {}),
Err(e) => {
Expand Down Expand Up @@ -195,6 +203,7 @@ mod tests_integration {
pubkey: "".to_string(),
name: commonname.clone(),
federation_id: "".to_string(),
unblinded_msg: "test_username_checker".to_string(),
federation_invite_code: "".to_string(),
};

Expand Down Expand Up @@ -237,7 +246,7 @@ mod tests_integration {
};

// generate valid blinded message
let msg = tbs::Message::from_bytes(b"Hello World!");
let msg = tbs::Message::from_bytes(b"register_username_tests");
let blinding_key = BlindingKey::random();
let blinded_msg = blind_message(msg, blinding_key);
let blind_sig = signer.blind_sign(blinded_msg);
Expand Down Expand Up @@ -303,7 +312,7 @@ mod tests_integration {

// generate valid blinded message
let signer = BlindSigner::derive(&[0u8; 32], 0, 0);
let msg = tbs::Message::from_bytes(b"Hello World!");
let msg = tbs::Message::from_bytes(b"register_username_add_unknown_federation_tests");
let blinding_key = BlindingKey::random();
let blinded_msg = blind_message(msg, blinding_key);
let blind_sig = signer.blind_sign(blinded_msg);
Expand All @@ -330,4 +339,79 @@ mod tests_integration {
}
}
}

#[tokio::test]
pub async fn register_username_already_spent_token_tests() {
dotenv::dotenv().ok();
let pg_url = std::env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let db = setup_db(pg_url);

// swap out fm with a mock here since that's not what is being tested
let mut mock_mm = MockMultiMintWrapperTrait::new();
mock_mm
.expect_check_has_federation()
.times(1)
.returning(|_| true);

// nostr
let nostr_nsec_str = std::env::var("NSEC").expect("FM_DB_PATH must be set");
let nostr_sk = Keys::from_sk_str(&nostr_nsec_str).expect("Invalid NOSTR_SK");
let nostr = nostr_sdk::Client::new(&nostr_sk);

// create blind signer
let signer = BlindSigner::derive(&[0u8; 32], 0, 0);

let mock_mm = Arc::new(mock_mm);
let state = State {
db: db.clone(),
mm: mock_mm,
secp: Secp256k1::new(),
nostr,
auth_pk: signer.pk,
domain: "http://127.0.0.1:8080".to_string(),
};

// generate valid blinded message
let msg = tbs::Message::from_bytes(b"register_username_already_spent_token_tests");
let blinding_key = BlindingKey::random();
let blinded_msg = blind_message(msg, blinding_key);
let blind_sig = signer.blind_sign(blinded_msg);
let sig = unblind_signature(blinding_key, blind_sig);

let connect = InviteCode::new(
"ws://test1".parse().unwrap(),
PeerId::from_str("1").unwrap(),
FederationId::dummy(),
);
let req = RegisterRequest {
name: "registername1".to_string(),
pubkey: "".to_string(),
federation_id: connect.federation_id(),
federation_invite_code: connect.to_string(),
msg,
sig,
};

// let the first user register sucessfully
match register(&state, req).await {
Ok(_) => (),
Err(_) => {
panic!("shouldn't error")
}
}

// second username attempting to register with the same msg
let req2 = RegisterRequest {
name: "registername2".to_string(),
pubkey: "".to_string(),
federation_id: connect.federation_id(),
federation_invite_code: connect.to_string(),
msg,
sig,
};

if register(&state, req2).await.is_ok() {
panic!("should not succeed")
}
}
}
1 change: 1 addition & 0 deletions src/routes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ impl From<RegisterRequest> for NewAppUser {
pubkey: request.pubkey,
name: request.name,
federation_id: request.federation_id.to_string(),
unblinded_msg: request.msg.0.to_string(),
federation_invite_code: request.federation_invite_code,
}
}
Expand Down

0 comments on commit 70e849b

Please sign in to comment.