Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change federation #28

Merged
merged 1 commit into from
Apr 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions src/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ pub(crate) trait DBConnection {
fn get_user_by_name(&self, name: String) -> anyhow::Result<Option<AppUser>>;
fn get_user_by_id(&self, id: i32) -> anyhow::Result<Option<AppUser>>;
fn get_user_by_pubkey(&self, pubkey: String) -> anyhow::Result<Option<AppUser>>;
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<Option<AppUser>>;
fn insert_new_zap(&self, new_zap: Zap) -> anyhow::Result<Zap>;
fn get_zap_by_id(&self, id: i32) -> anyhow::Result<Option<Zap>>;
Expand Down Expand Up @@ -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<Vec<Invoice>> {
let conn = &mut self.db.get()?;
Invoice::get_by_state(conn, 0)
Expand Down
7 changes: 4 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};

Expand Down Expand Up @@ -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(
Expand Down
17 changes: 17 additions & 0 deletions src/models/app_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,23 @@ impl AppUser {
.first::<AppUser>(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)]
Expand Down
46 changes: 29 additions & 17 deletions src/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ pub fn get_user_by_pubkey(state: &State, pubkey: String) -> anyhow::Result<Optio
state.db.get_user_by_pubkey(pubkey)
}

pub fn change_user_federation(
state: &State,
user: AppUser,
federation_id: String,
federation_invite_code: String,
) -> anyhow::Result<()> {
state
.db
.update_user_federation(user, federation_id, federation_invite_code)
}

pub fn generate_random_name(state: &State) -> anyhow::Result<String> {
loop {
let new_name = Generator::with_naming(names::Name::Numbered)
Expand Down Expand Up @@ -122,23 +133,7 @@ pub async fn register(
}
};
let federation_id = invite_code.federation_id();
if !state.mm.check_has_federation(federation_id).await {
let invite_code = match InviteCode::from_str(&req.federation_invite_code) {
Ok(i) => i,
Err(e) => {
error!("Error in register: {e:?}");
return Err((StatusCode::BAD_REQUEST, "InvalidFederation".to_string()));
}
};

match state.mm.register_new_federation(invite_code).await {
Ok(_) => (),
Err(e) => {
error!("Error in register: {e:?}");
return Err((StatusCode::BAD_REQUEST, "InvalidFederation".to_string()));
}
}
}
ensure_added_federation(state, federation_id, invite_code).await?;

let new_user = NewAppUser {
pubkey: req.pubkey,
Expand All @@ -158,6 +153,23 @@ pub async fn register(
}
}

pub(crate) async fn ensure_added_federation(
state: &State,
federation_id: fedimint_core::config::FederationId,
invite_code: InviteCode,
) -> Result<(), (StatusCode, String)> {
if !state.mm.check_has_federation(federation_id).await {
match state.mm.register_new_federation(invite_code).await {
Ok(_) => (),
Err(e) => {
error!("Error in register: {e:?}");
return Err((StatusCode::BAD_REQUEST, "InvalidFederation".to_string()));
}
}
}
Ok(())
}

#[cfg(all(test, not(feature = "integration-tests")))]
mod tests {
use crate::register::is_valid_name;
Expand Down
81 changes: 77 additions & 4 deletions src/routes.rs
Original file line number Diff line number Diff line change
@@ -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, ensure_added_federation,
get_user_by_pubkey, register,
},
State, ALLOWED_LOCALHOST, ALLOWED_ORIGINS, ALLOWED_SUBDOMAIN, API_VERSION,
};
use axum::extract::{Path, Query};
Expand All @@ -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};
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand All @@ -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) => {
Expand All @@ -126,6 +135,70 @@ pub async fn check_registration_info(
}
}

pub async fn change_federation(
origin: Option<TypedHeader<Origin>>,
Extension(state): Extension<State>,
Json(event): Json<Event>,
) -> 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();

// make sure it's added to our federation list
ensure_added_federation(&state, federation_id, federation_invite_code.clone()).await?;

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!(
TonyGiorgio marked this conversation as resolved.
Show resolved Hide resolved
"change_federation changed user federation for pubkey: {}, {}",
pubkey, federation_id
);
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<String>,
Expand Down