From 0d28f86378790c9eb4a0ec05d4d28c0afd8e08be Mon Sep 17 00:00:00 2001 From: Tony Giorgio Date: Wed, 10 Apr 2024 21:36:51 -0500 Subject: [PATCH] Change federation --- src/db.rs | 16 +++++++++ src/main.rs | 7 ++-- src/models/app_user.rs | 17 +++++++++ src/register.rs | 11 ++++++ src/routes.rs | 79 +++++++++++++++++++++++++++++++++++++++--- 5 files changed, 123 insertions(+), 7 deletions(-) diff --git a/src/db.rs b/src/db.rs index 14e70b0..61aa75e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -23,6 +23,12 @@ pub(crate) trait DBConnection { fn get_user_by_name(&self, name: String) -> anyhow::Result>; fn get_user_by_id(&self, id: i32) -> anyhow::Result>; fn get_user_by_pubkey(&self, pubkey: String) -> anyhow::Result>; + fn update_user_federation( + &self, + user: AppUser, + federation_id: String, + federation_invite_code: String, + ) -> anyhow::Result<()>; fn get_user_and_increment_counter(&self, name: &str) -> anyhow::Result>; fn insert_new_zap(&self, new_zap: Zap) -> anyhow::Result; fn get_zap_by_id(&self, id: i32) -> anyhow::Result>; @@ -57,6 +63,16 @@ impl DBConnection for PostgresConnection { new_user.insert(conn) } + fn update_user_federation( + &self, + user: AppUser, + federation_id: String, + federation_invite_code: String, + ) -> anyhow::Result<()> { + let conn = &mut self.db.get()?; + user.update_federation(conn, federation_id, federation_invite_code) + } + fn get_pending_invoices(&self) -> anyhow::Result> { let conn = &mut self.db.get()?; Invoice::get_by_state(conn, 0) diff --git a/src/main.rs b/src/main.rs index 924c1d0..44cccd2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,9 +17,9 @@ use crate::{ invoice::handle_pending_invoices, mint::{setup_multimint, MultiMintWrapperTrait}, routes::{ - check_pubkey, check_registration_info, check_username, health_check, lnurl_callback_route, - lnurl_verify_route, register_route, root, validate_cors, well_known_lnurlp_route, - well_known_nip5_route, + change_federation, check_pubkey, check_registration_info, check_username, health_check, + lnurl_callback_route, lnurl_verify_route, register_route, root, validate_cors, + well_known_lnurlp_route, well_known_nip5_route, }, }; @@ -171,6 +171,7 @@ async fn main() -> anyhow::Result<()> { .route("/v1/check-username/:username", get(check_username)) .route("/v1/check-pubkey/:pubkey", get(check_pubkey)) // DEPRECATED for check-registration .route("/v1/check-registration", post(check_registration_info)) + .route("/v1/change-federation", post(change_federation)) .route("/v1/register", post(register_route)) .route("/.well-known/nostr.json", get(well_known_nip5_route)) .route( diff --git a/src/models/app_user.rs b/src/models/app_user.rs index b5cb1f4..4863e96 100644 --- a/src/models/app_user.rs +++ b/src/models/app_user.rs @@ -87,6 +87,23 @@ impl AppUser { .first::(conn) .optional()?) } + + pub fn update_federation( + &self, + conn: &mut PgConnection, + new_federation_id: String, + new_federation_invite_code: String, + ) -> anyhow::Result<()> { + diesel::update(app_user::table) + .filter(app_user::name.eq(&self.name)) + .set(( + app_user::federation_id.eq(new_federation_id), + app_user::federation_invite_code.eq(new_federation_invite_code), + )) + .execute(conn)?; + + Ok(()) + } } #[derive(Insertable)] diff --git a/src/register.rs b/src/register.rs index 884aa57..42adb7d 100644 --- a/src/register.rs +++ b/src/register.rs @@ -39,6 +39,17 @@ pub fn get_user_by_pubkey(state: &State, pubkey: String) -> anyhow::Result anyhow::Result<()> { + state + .db + .update_user_federation(user, federation_id, federation_invite_code) +} + pub fn generate_random_name(state: &State) -> anyhow::Result { loop { let new_name = Generator::with_naming(names::Name::Numbered) diff --git a/src/routes.rs b/src/routes.rs index 1af65c9..71108b5 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -1,7 +1,10 @@ use crate::{ lnurlp::{lnurl_callback, verify, well_known_lnurlp}, nostr::well_known_nip5, - register::{check_available, check_registered_pubkey, get_user_by_pubkey, register}, + register::{ + change_user_federation, check_available, check_registered_pubkey, get_user_by_pubkey, + register, + }, State, ALLOWED_LOCALHOST, ALLOWED_ORIGINS, ALLOWED_SUBDOMAIN, API_VERSION, }; use axum::extract::{Path, Query}; @@ -10,7 +13,7 @@ use axum::http::StatusCode; use axum::response::{IntoResponse, Redirect, Response}; use axum::Extension; use axum::{Json, TypedHeader}; -use fedimint_core::{config::FederationId, Amount}; +use fedimint_core::{api::InviteCode, config::FederationId, Amount}; use fedimint_ln_common::lightning_invoice::Bolt11Invoice; use log::{error, info}; use nostr::{Event, Kind}; @@ -21,6 +24,7 @@ use tbs::AggregatePublicKey; use url::Url; const REGISTRATION_CHECK_EVENT_KIND: Kind = Kind::Custom(93_186); +const NEW_FEDERATION_EVENT_KIND: Kind = Kind::Custom(93_187); #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct LnUrlErrorResponse { @@ -94,7 +98,7 @@ pub async fn check_registration_info( return Err((StatusCode::BAD_REQUEST, "Bad event".to_string())); } - // make sure it was made in the last 30 seconds + // make sure it was made recently let created_at = event.created_at(); let now = nostr::Timestamp::now(); if created_at < now - 120_i64 && created_at > now + 120_i64 { @@ -111,7 +115,12 @@ pub async fn check_registration_info( Ok(Json(RegistrationInfo { name: Some(u.name), - federation_id: Some(u.federation_id), + federation_id: Some(FederationId::from_str(&u.federation_id).map_err(|_| { + ( + StatusCode::INTERNAL_SERVER_ERROR, + "FederationId invalid".to_string(), + ) + })?), })) } Ok(None) => { @@ -126,6 +135,68 @@ pub async fn check_registration_info( } } +pub async fn change_federation( + origin: Option>, + Extension(state): Extension, + Json(event): Json, +) -> Result<(), (StatusCode, String)> { + validate_cors(origin)?; + + let pubkey = event.author(); + info!("change_federation: {}", pubkey); + + if event.verify().is_err() && event.kind() != NEW_FEDERATION_EVENT_KIND { + error!("error in change_federation: bad event"); + return Err((StatusCode::BAD_REQUEST, "Bad event".to_string())); + } + + // make sure it was made recently + let created_at = event.created_at(); + let now = nostr::Timestamp::now(); + if created_at < now - 120_i64 && created_at > now + 120_i64 { + error!("error in change_federation: event time not in range"); + return Err(( + StatusCode::BAD_REQUEST, + "Event time not in range".to_string(), + )); + } + + // get the federation invite code and parse it + let federation_invite_code = InviteCode::from_str(event.content()) + .map_err(|_| (StatusCode::BAD_REQUEST, "InviteCode Invalid".to_string()))?; + let federation_id = federation_invite_code.federation_id(); + + match get_user_by_pubkey(&state, pubkey.to_string()) { + Ok(Some(u)) => { + info!("change_federation found user for pubkey: {}", pubkey); + + // got the user, now change the federation + match change_user_federation( + &state, + u, + federation_id.to_string(), + federation_invite_code.to_string(), + ) { + Ok(_) => { + info!( + "change_federation changed user federation for pubkey: {}, {}", + pubkey, + federation_id.to_string() + ); + Ok(()) + } + Err(e) => Err(handle_anyhow_error("change_federation", e)), + } + } + Ok(None) => { + error!("change_federation not found: {}", pubkey); + + Err((StatusCode::NOT_FOUND, "User not found".to_string())) + } + Err(e) => Err(handle_anyhow_error("change_federation", e)), + } +} + #[derive(Deserialize, Clone)] pub struct RegisterRequest { pub name: Option,