From df00a14064d3210390db7d8f7c4a6b76948c88b0 Mon Sep 17 00:00:00 2001 From: tusharshah21 Date: Fri, 10 Oct 2025 08:11:56 +0000 Subject: [PATCH 1/5] feat: Implement SIWE nonce-based authentication --- ...f0bff9dcf5f133ecb58bf2706104e55d3490.json} | 12 +++-- ...9e28f8b95cedbd9da3b5a7f2b5ced0505462.json} | 12 +++-- ...640e73d70dde72e7412ca265b30db5ac4249.json} | 12 +++-- ...8ed4aab6d7f3a5f5d88ae151cb6c21666761d.json | 22 ++++++++ ...d020c23a6650ec0bac46c56c30bc3d9c8142.json} | 5 +- ...96ded14179f08a15e3235d3e95b95964bd2c7.json | 14 +++++ backend/migrations/003_add_nonces.sql | 3 ++ .../application/commands/get_login_nonce.rs | 19 +++++++ backend/src/application/commands/mod.rs | 1 + backend/src/application/dtos/auth_dtos.rs | 10 ++++ backend/src/domain/entities/profile.rs | 2 + .../domain/repositories/profile_repository.rs | 8 +++ backend/src/domain/services/auth_service.rs | 2 +- .../postgres_profile_repository.rs | 52 +++++++++++++++++-- .../ethereum_address_verification_service.rs | 31 ++++++----- backend/src/presentation/api.rs | 12 +++-- backend/src/presentation/handlers.rs | 19 ++++++- backend/src/presentation/middlewares.rs | 19 +++++-- backend/tests/integration_github_handle.rs | 27 +++++----- backend/tests/profile_tests.rs | 18 +++++++ 20 files changed, 248 insertions(+), 52 deletions(-) rename backend/.sqlx/{query-fd6f338fcae9c81fbf1d7590574fa950a74fa68daabb48c80a0a7754e4066987.json => query-21d41f830b745d6af6e1319ee36ef0bff9dcf5f133ecb58bf2706104e55d3490.json} (74%) rename backend/.sqlx/{query-b521c6c7f362753693d7059c6815de444a5c6aadc1a9950d9d71f49f52dee768.json => query-5b21241db59e84080d36c67abb309e28f8b95cedbd9da3b5a7f2b5ced0505462.json} (77%) rename backend/.sqlx/{query-177358fec702a5f78c1ff0dbd5eed42fa868487c84ebef42dfcf695e9ce42725.json => query-d23893061ef0e487ff20489fc5f4640e73d70dde72e7412ca265b30db5ac4249.json} (75%) create mode 100644 backend/.sqlx/query-d79097da6ad7c82305d728beb248ed4aab6d7f3a5f5d88ae151cb6c21666761d.json rename backend/.sqlx/{query-7acd8c9bc567ef80f66a38130bb708068882a4559856e38e6231405e9acc5a74.json => query-e4c05cdcfce8dddaf75c30de7e20d020c23a6650ec0bac46c56c30bc3d9c8142.json} (58%) create mode 100644 backend/.sqlx/query-fe47a9ce9d61692552d87b31b3596ded14179f08a15e3235d3e95b95964bd2c7.json create mode 100644 backend/migrations/003_add_nonces.sql create mode 100644 backend/src/application/commands/get_login_nonce.rs diff --git a/backend/.sqlx/query-fd6f338fcae9c81fbf1d7590574fa950a74fa68daabb48c80a0a7754e4066987.json b/backend/.sqlx/query-21d41f830b745d6af6e1319ee36ef0bff9dcf5f133ecb58bf2706104e55d3490.json similarity index 74% rename from backend/.sqlx/query-fd6f338fcae9c81fbf1d7590574fa950a74fa68daabb48c80a0a7754e4066987.json rename to backend/.sqlx/query-21d41f830b745d6af6e1319ee36ef0bff9dcf5f133ecb58bf2706104e55d3490.json index 7e5ec3a..bb66029 100644 --- a/backend/.sqlx/query-fd6f338fcae9c81fbf1d7590574fa950a74fa68daabb48c80a0a7754e4066987.json +++ b/backend/.sqlx/query-21d41f830b745d6af6e1319ee36ef0bff9dcf5f133ecb58bf2706104e55d3490.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT address, name, description, avatar_url, github_login, created_at, updated_at\n FROM profiles\n WHERE address = $1\n ", + "query": "\n SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at\n FROM profiles\n WHERE LOWER(github_login) = LOWER($1)\n ", "describe": { "columns": [ { @@ -30,11 +30,16 @@ }, { "ordinal": 5, + "name": "login_nonce", + "type_info": "Int8" + }, + { + "ordinal": 6, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 6, + "ordinal": 7, "name": "updated_at", "type_info": "Timestamptz" } @@ -50,9 +55,10 @@ true, true, true, + false, true, true ] }, - "hash": "fd6f338fcae9c81fbf1d7590574fa950a74fa68daabb48c80a0a7754e4066987" + "hash": "21d41f830b745d6af6e1319ee36ef0bff9dcf5f133ecb58bf2706104e55d3490" } diff --git a/backend/.sqlx/query-b521c6c7f362753693d7059c6815de444a5c6aadc1a9950d9d71f49f52dee768.json b/backend/.sqlx/query-5b21241db59e84080d36c67abb309e28f8b95cedbd9da3b5a7f2b5ced0505462.json similarity index 77% rename from backend/.sqlx/query-b521c6c7f362753693d7059c6815de444a5c6aadc1a9950d9d71f49f52dee768.json rename to backend/.sqlx/query-5b21241db59e84080d36c67abb309e28f8b95cedbd9da3b5a7f2b5ced0505462.json index 0e00234..076074a 100644 --- a/backend/.sqlx/query-b521c6c7f362753693d7059c6815de444a5c6aadc1a9950d9d71f49f52dee768.json +++ b/backend/.sqlx/query-5b21241db59e84080d36c67abb309e28f8b95cedbd9da3b5a7f2b5ced0505462.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT address, name, description, avatar_url, github_login, created_at, updated_at\n FROM profiles\n ", + "query": "\n SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at\n FROM profiles\n ", "describe": { "columns": [ { @@ -30,11 +30,16 @@ }, { "ordinal": 5, + "name": "login_nonce", + "type_info": "Int8" + }, + { + "ordinal": 6, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 6, + "ordinal": 7, "name": "updated_at", "type_info": "Timestamptz" } @@ -48,9 +53,10 @@ true, true, true, + false, true, true ] }, - "hash": "b521c6c7f362753693d7059c6815de444a5c6aadc1a9950d9d71f49f52dee768" + "hash": "5b21241db59e84080d36c67abb309e28f8b95cedbd9da3b5a7f2b5ced0505462" } diff --git a/backend/.sqlx/query-177358fec702a5f78c1ff0dbd5eed42fa868487c84ebef42dfcf695e9ce42725.json b/backend/.sqlx/query-d23893061ef0e487ff20489fc5f4640e73d70dde72e7412ca265b30db5ac4249.json similarity index 75% rename from backend/.sqlx/query-177358fec702a5f78c1ff0dbd5eed42fa868487c84ebef42dfcf695e9ce42725.json rename to backend/.sqlx/query-d23893061ef0e487ff20489fc5f4640e73d70dde72e7412ca265b30db5ac4249.json index 020138d..3329f2f 100644 --- a/backend/.sqlx/query-177358fec702a5f78c1ff0dbd5eed42fa868487c84ebef42dfcf695e9ce42725.json +++ b/backend/.sqlx/query-d23893061ef0e487ff20489fc5f4640e73d70dde72e7412ca265b30db5ac4249.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT address, name, description, avatar_url, github_login, created_at, updated_at\n FROM profiles\n WHERE LOWER(github_login) = LOWER($1)\n ", + "query": "\n SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at\n FROM profiles\n WHERE address = $1\n ", "describe": { "columns": [ { @@ -30,11 +30,16 @@ }, { "ordinal": 5, + "name": "login_nonce", + "type_info": "Int8" + }, + { + "ordinal": 6, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 6, + "ordinal": 7, "name": "updated_at", "type_info": "Timestamptz" } @@ -50,9 +55,10 @@ true, true, true, + false, true, true ] }, - "hash": "177358fec702a5f78c1ff0dbd5eed42fa868487c84ebef42dfcf695e9ce42725" + "hash": "d23893061ef0e487ff20489fc5f4640e73d70dde72e7412ca265b30db5ac4249" } diff --git a/backend/.sqlx/query-d79097da6ad7c82305d728beb248ed4aab6d7f3a5f5d88ae151cb6c21666761d.json b/backend/.sqlx/query-d79097da6ad7c82305d728beb248ed4aab6d7f3a5f5d88ae151cb6c21666761d.json new file mode 100644 index 0000000..08b1944 --- /dev/null +++ b/backend/.sqlx/query-d79097da6ad7c82305d728beb248ed4aab6d7f3a5f5d88ae151cb6c21666761d.json @@ -0,0 +1,22 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT login_nonce\n FROM profiles\n WHERE address = $1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "login_nonce", + "type_info": "Int8" + } + ], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [ + false + ] + }, + "hash": "d79097da6ad7c82305d728beb248ed4aab6d7f3a5f5d88ae151cb6c21666761d" +} diff --git a/backend/.sqlx/query-7acd8c9bc567ef80f66a38130bb708068882a4559856e38e6231405e9acc5a74.json b/backend/.sqlx/query-e4c05cdcfce8dddaf75c30de7e20d020c23a6650ec0bac46c56c30bc3d9c8142.json similarity index 58% rename from backend/.sqlx/query-7acd8c9bc567ef80f66a38130bb708068882a4559856e38e6231405e9acc5a74.json rename to backend/.sqlx/query-e4c05cdcfce8dddaf75c30de7e20d020c23a6650ec0bac46c56c30bc3d9c8142.json index 634fb2e..86f6186 100644 --- a/backend/.sqlx/query-7acd8c9bc567ef80f66a38130bb708068882a4559856e38e6231405e9acc5a74.json +++ b/backend/.sqlx/query-e4c05cdcfce8dddaf75c30de7e20d020c23a6650ec0bac46c56c30bc3d9c8142.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO profiles (address, name, description, avatar_url, github_login, created_at, updated_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\n ", + "query": "\n INSERT INTO profiles (address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8)\n ", "describe": { "columns": [], "parameters": { @@ -10,11 +10,12 @@ "Text", "Text", "Text", + "Int8", "Timestamptz", "Timestamptz" ] }, "nullable": [] }, - "hash": "7acd8c9bc567ef80f66a38130bb708068882a4559856e38e6231405e9acc5a74" + "hash": "e4c05cdcfce8dddaf75c30de7e20d020c23a6650ec0bac46c56c30bc3d9c8142" } diff --git a/backend/.sqlx/query-fe47a9ce9d61692552d87b31b3596ded14179f08a15e3235d3e95b95964bd2c7.json b/backend/.sqlx/query-fe47a9ce9d61692552d87b31b3596ded14179f08a15e3235d3e95b95964bd2c7.json new file mode 100644 index 0000000..afb34ed --- /dev/null +++ b/backend/.sqlx/query-fe47a9ce9d61692552d87b31b3596ded14179f08a15e3235d3e95b95964bd2c7.json @@ -0,0 +1,14 @@ +{ + "db_name": "PostgreSQL", + "query": "\n UPDATE profiles\n SET login_nonce = login_nonce + 1\n WHERE address = $1\n ", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Text" + ] + }, + "nullable": [] + }, + "hash": "fe47a9ce9d61692552d87b31b3596ded14179f08a15e3235d3e95b95964bd2c7" +} diff --git a/backend/migrations/003_add_nonces.sql b/backend/migrations/003_add_nonces.sql new file mode 100644 index 0000000..a8e4b55 --- /dev/null +++ b/backend/migrations/003_add_nonces.sql @@ -0,0 +1,3 @@ +-- Add login_nonce column to profiles table +-- The nonce starts at 1 and increments with each successful login +ALTER TABLE profiles ADD COLUMN IF NOT EXISTS login_nonce BIGINT NOT NULL DEFAULT 1; diff --git a/backend/src/application/commands/get_login_nonce.rs b/backend/src/application/commands/get_login_nonce.rs new file mode 100644 index 0000000..5e33796 --- /dev/null +++ b/backend/src/application/commands/get_login_nonce.rs @@ -0,0 +1,19 @@ +use std::sync::Arc; + +use crate::domain::{repositories::ProfileRepository, value_objects::WalletAddress}; + +pub async fn get_login_nonce( + profile_repository: Arc, + address: String, +) -> Result { + let wallet_address = WalletAddress(address); + + match profile_repository + .get_login_nonce_by_wallet_address(&wallet_address) + .await + { + Ok(Some(nonce)) => Ok(nonce), + Ok(None) => Err("Profile not found".to_string()), + Err(e) => Err(format!("Error fetching nonce: {}", e)), + } +} diff --git a/backend/src/application/commands/mod.rs b/backend/src/application/commands/mod.rs index ac3cc0c..29342de 100644 --- a/backend/src/application/commands/mod.rs +++ b/backend/src/application/commands/mod.rs @@ -1,4 +1,5 @@ pub mod create_profile; pub mod get_all_profiles; +pub mod get_login_nonce; pub mod get_profile; pub mod update_profile; diff --git a/backend/src/application/dtos/auth_dtos.rs b/backend/src/application/dtos/auth_dtos.rs index 8b38ce4..c94f36c 100644 --- a/backend/src/application/dtos/auth_dtos.rs +++ b/backend/src/application/dtos/auth_dtos.rs @@ -1,10 +1,20 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] pub struct VerifyMessageRequest { pub address: String, pub nonce: String, pub message: String, } +#[derive(Debug, Serialize, Deserialize)] pub struct VerifyMessageResponse { pub success: bool, pub address: String, } + +#[derive(Debug, Serialize, Deserialize)] +pub struct NonceResponse { + pub nonce: i64, + pub address: String, +} diff --git a/backend/src/domain/entities/profile.rs b/backend/src/domain/entities/profile.rs index c13301d..fd42982 100644 --- a/backend/src/domain/entities/profile.rs +++ b/backend/src/domain/entities/profile.rs @@ -10,6 +10,7 @@ pub struct Profile { pub description: Option, pub avatar_url: Option, pub github_login: Option, + pub login_nonce: i64, pub created_at: DateTime, pub updated_at: DateTime, } @@ -23,6 +24,7 @@ impl Profile { description: None, avatar_url: None, github_login: None, + login_nonce: 1, created_at: now, updated_at: now, } diff --git a/backend/src/domain/repositories/profile_repository.rs b/backend/src/domain/repositories/profile_repository.rs index 61b6245..3658458 100644 --- a/backend/src/domain/repositories/profile_repository.rs +++ b/backend/src/domain/repositories/profile_repository.rs @@ -16,4 +16,12 @@ pub trait ProfileRepository: Send + Sync { &self, github_login: &str, ) -> Result, Box>; + async fn get_login_nonce_by_wallet_address( + &self, + address: &WalletAddress, + ) -> Result, Box>; + async fn increment_login_nonce( + &self, + address: &WalletAddress, + ) -> Result<(), Box>; } diff --git a/backend/src/domain/services/auth_service.rs b/backend/src/domain/services/auth_service.rs index 3afaf9f..2ca750c 100644 --- a/backend/src/domain/services/auth_service.rs +++ b/backend/src/domain/services/auth_service.rs @@ -4,7 +4,7 @@ use crate::domain::value_objects::wallet_address::WalletAddress; #[derive(Debug, Clone, PartialEq, Eq)] pub struct AuthChallenge { - pub nonce: String, + pub nonce: i64, pub address: String, } diff --git a/backend/src/infrastructure/repositories/postgres_profile_repository.rs b/backend/src/infrastructure/repositories/postgres_profile_repository.rs index b480585..e951f8e 100644 --- a/backend/src/infrastructure/repositories/postgres_profile_repository.rs +++ b/backend/src/infrastructure/repositories/postgres_profile_repository.rs @@ -24,7 +24,7 @@ impl ProfileRepository for PostgresProfileRepository { ) -> Result, Box> { let row = sqlx::query!( r#" - SELECT address, name, description, avatar_url, github_login, created_at, updated_at + SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at FROM profiles WHERE address = $1 "#, @@ -40,6 +40,7 @@ impl ProfileRepository for PostgresProfileRepository { description: r.description, avatar_url: r.avatar_url, github_login: r.github_login, + login_nonce: r.login_nonce, created_at: r.created_at.unwrap(), updated_at: r.updated_at.unwrap(), })) @@ -48,7 +49,7 @@ impl ProfileRepository for PostgresProfileRepository { async fn find_all(&self) -> Result, Box> { let rows = sqlx::query!( r#" - SELECT address, name, description, avatar_url, github_login, created_at, updated_at + SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at FROM profiles "#, ) @@ -64,6 +65,7 @@ impl ProfileRepository for PostgresProfileRepository { description: r.description, avatar_url: r.avatar_url, github_login: r.github_login, + login_nonce: r.login_nonce, created_at: r.created_at.unwrap(), updated_at: r.updated_at.unwrap(), }) @@ -73,14 +75,15 @@ impl ProfileRepository for PostgresProfileRepository { async fn create(&self, profile: &Profile) -> Result<(), Box> { sqlx::query!( r#" - INSERT INTO profiles (address, name, description, avatar_url, github_login, created_at, updated_at) - VALUES ($1, $2, $3, $4, $5, $6, $7) + INSERT INTO profiles (address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8) "#, profile.address.as_str(), profile.name, profile.description, profile.avatar_url, profile.github_login, + profile.login_nonce, profile.created_at, profile.updated_at ) @@ -133,7 +136,7 @@ impl ProfileRepository for PostgresProfileRepository { ) -> Result, Box> { let row = sqlx::query!( r#" - SELECT address, name, description, avatar_url, github_login, created_at, updated_at + SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at FROM profiles WHERE LOWER(github_login) = LOWER($1) "#, @@ -149,8 +152,47 @@ impl ProfileRepository for PostgresProfileRepository { description: r.description, avatar_url: r.avatar_url, github_login: r.github_login, + login_nonce: r.login_nonce, created_at: r.created_at.unwrap(), updated_at: r.updated_at.unwrap(), })) } + + async fn get_login_nonce_by_wallet_address( + &self, + address: &WalletAddress, + ) -> Result, Box> { + let row = sqlx::query!( + r#" + SELECT login_nonce + FROM profiles + WHERE address = $1 + "#, + address.as_str() + ) + .fetch_optional(&self.pool) + .await + .map_err(|e| Box::new(e) as Box)?; + + Ok(row.map(|r| r.login_nonce)) + } + + async fn increment_login_nonce( + &self, + address: &WalletAddress, + ) -> Result<(), Box> { + sqlx::query!( + r#" + UPDATE profiles + SET login_nonce = login_nonce + 1 + WHERE address = $1 + "#, + address.as_str() + ) + .execute(&self.pool) + .await + .map_err(|e| Box::new(e) as Box)?; + + Ok(()) + } } diff --git a/backend/src/infrastructure/services/ethereum_address_verification_service.rs b/backend/src/infrastructure/services/ethereum_address_verification_service.rs index e8ff93f..35d740c 100644 --- a/backend/src/infrastructure/services/ethereum_address_verification_service.rs +++ b/backend/src/infrastructure/services/ethereum_address_verification_service.rs @@ -2,21 +2,21 @@ use async_trait::async_trait; use ethers::core::utils::hash_message; use ethers::types::{Address, Signature}; use std::str::FromStr; +use std::sync::Arc; +use crate::domain::repositories::ProfileRepository; use crate::domain::services::auth_service::{AuthChallenge, AuthResult, AuthService}; use crate::domain::value_objects::WalletAddress; -pub struct EthereumAddressVerificationService {} - -impl EthereumAddressVerificationService { - pub fn new() -> Self { - Self {} - } +pub struct EthereumAddressVerificationService { + profile_repository: Arc, } -impl Default for EthereumAddressVerificationService { - fn default() -> Self { - Self::new() +impl EthereumAddressVerificationService { + pub fn new(profile_repository: Arc) -> Self { + Self { + profile_repository, + } } } @@ -27,10 +27,11 @@ impl AuthService for EthereumAddressVerificationService { challenge: &AuthChallenge, signature: &str, ) -> Result, Box> { - const EXPECTED_MSG: &str = "LOGIN_NONCE"; // or whatever constant string you are signing + // Create the message with the nonce + let message = format!("Sign this message to authenticate with The Guild.\n\nNonce: {}", challenge.nonce); // EIP-191 prefix + keccak256 - let msg_hash = hash_message(EXPECTED_MSG); + let msg_hash = hash_message(message); // Parse signature and expected address let sig = Signature::from_str(signature)?; @@ -40,8 +41,14 @@ impl AuthService for EthereumAddressVerificationService { let recovered = sig.recover(msg_hash)?; if recovered == expected { + // Increment the nonce after successful verification + let wallet_address = WalletAddress(challenge.address.clone()); + self.profile_repository + .increment_login_nonce(&wallet_address) + .await?; + Ok(Some(AuthResult { - wallet_address: WalletAddress(challenge.address.clone()), + wallet_address, })) } else { Ok(None) diff --git a/backend/src/presentation/api.rs b/backend/src/presentation/api.rs index 09fc099..b712dfd 100644 --- a/backend/src/presentation/api.rs +++ b/backend/src/presentation/api.rs @@ -20,18 +20,18 @@ use tower_http::{ }; use super::handlers::{ - create_profile_handler, delete_profile_handler, get_all_profiles_handler, get_profile_handler, - update_profile_handler, + create_profile_handler, delete_profile_handler, get_all_profiles_handler, get_nonce_handler, + get_profile_handler, update_profile_handler, }; use super::middlewares::{eth_auth_layer, test_auth_layer}; pub async fn create_app(pool: sqlx::PgPool) -> Router { - let auth_service = EthereumAddressVerificationService::new(); - let profile_repository = PostgresProfileRepository::new(pool); + let profile_repository = Arc::from(PostgresProfileRepository::new(pool)); + let auth_service = EthereumAddressVerificationService::new(profile_repository.clone()); let state: AppState = AppState { - profile_repository: Arc::from(profile_repository), + profile_repository, auth_service: Arc::from(auth_service), }; @@ -50,6 +50,7 @@ pub async fn create_app(pool: sqlx::PgPool) -> Router { let public_routes = Router::new() .route("/profiles/:address", get(get_profile_handler)) .route("/profiles", get(get_all_profiles_handler)) + .route("/auth/nonce/:address", get(get_nonce_handler)) .with_state(state.clone()); Router::new() @@ -86,6 +87,7 @@ pub fn test_api(state: AppState) -> Router { let public_routes = Router::new() .route("/profiles/:address", get(get_profile_handler)) .route("/profiles", get(get_all_profiles_handler)) + .route("/auth/nonce/:address", get(get_nonce_handler)) .with_state(state.clone()); Router::new() diff --git a/backend/src/presentation/handlers.rs b/backend/src/presentation/handlers.rs index ffc1387..c02fd9b 100644 --- a/backend/src/presentation/handlers.rs +++ b/backend/src/presentation/handlers.rs @@ -9,9 +9,10 @@ use crate::{ application::{ commands::{ create_profile::create_profile, get_all_profiles::get_all_profiles, - get_profile::get_profile, update_profile::update_profile, + get_login_nonce::get_login_nonce, get_profile::get_profile, + update_profile::update_profile, }, - dtos::{CreateProfileRequest, ProfileResponse, UpdateProfileRequest}, + dtos::{CreateProfileRequest, NonceResponse, ProfileResponse, UpdateProfileRequest}, }, domain::value_objects::WalletAddress, }; @@ -76,3 +77,17 @@ pub async fn delete_profile_handler( .unwrap(); StatusCode::ACCEPTED } + +pub async fn get_nonce_handler( + State(state): State, + Path(address): Path, +) -> impl IntoResponse { + match get_login_nonce(state.profile_repository, address.clone()).await { + Ok(nonce) => Json(NonceResponse { + nonce, + address, + }) + .into_response(), + Err(e) => (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": e}))).into_response(), + } +} diff --git a/backend/src/presentation/middlewares.rs b/backend/src/presentation/middlewares.rs index 903dd32..ab07ee9 100644 --- a/backend/src/presentation/middlewares.rs +++ b/backend/src/presentation/middlewares.rs @@ -43,20 +43,31 @@ pub async fn eth_auth_layer( .map(str::to_owned) .ok_or(StatusCode::UNAUTHORIZED)?; - let nonce = "NONCE"; + // Get the current nonce from the database + let wallet_address = crate::domain::value_objects::WalletAddress(address.clone()); + let nonce = state + .profile_repository + .get_login_nonce_by_wallet_address(&wallet_address) + .await + .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? + .ok_or(StatusCode::UNAUTHORIZED)?; // Profile must exist - state + let result = state .auth_service .verify_signature( &AuthChallenge { address: address.clone().to_string(), - nonce: nonce.to_string(), + nonce, }, &signature, - ) // define the signature you like + ) .await .map_err(|_| StatusCode::UNAUTHORIZED)?; + if result.is_none() { + return Err(StatusCode::UNAUTHORIZED); + } + // Inject identity for handlers: req.extensions_mut() .insert(VerifiedWallet(address.to_string())); diff --git a/backend/tests/integration_github_handle.rs b/backend/tests/integration_github_handle.rs index bd41e0b..4c00b44 100644 --- a/backend/tests/integration_github_handle.rs +++ b/backend/tests/integration_github_handle.rs @@ -13,11 +13,12 @@ async fn valid_github_handle_works() { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); - let profile_repository = - guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()); - let auth_service = guild_backend::infrastructure::services::ethereum_address_verification_service::EthereumAddressVerificationService::new(); + let profile_repository = std::sync::Arc::new( + guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()) + ); + let auth_service = guild_backend::infrastructure::services::ethereum_address_verification_service::EthereumAddressVerificationService::new(profile_repository.clone()); let state = AppState { - profile_repository: std::sync::Arc::new(profile_repository), + profile_repository, auth_service: std::sync::Arc::new(auth_service), }; let app = test_api(state); @@ -80,11 +81,12 @@ async fn invalid_format_rejected() { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); - let profile_repository = - guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()); - let auth_service = guild_backend::infrastructure::services::ethereum_address_verification_service::EthereumAddressVerificationService::new(); + let profile_repository = std::sync::Arc::new( + guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()) + ); + let auth_service = guild_backend::infrastructure::services::ethereum_address_verification_service::EthereumAddressVerificationService::new(profile_repository.clone()); let state = AppState { - profile_repository: std::sync::Arc::new(profile_repository), + profile_repository, auth_service: std::sync::Arc::new(auth_service), }; let app = test_api(state); @@ -154,11 +156,12 @@ async fn conflict_case_insensitive() { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let addr = listener.local_addr().unwrap(); let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); - let profile_repository = - guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()); - let auth_service = guild_backend::infrastructure::services::ethereum_address_verification_service::EthereumAddressVerificationService::new(); + let profile_repository = std::sync::Arc::new( + guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()) + ); + let auth_service = guild_backend::infrastructure::services::ethereum_address_verification_service::EthereumAddressVerificationService::new(profile_repository.clone()); let state = AppState { - profile_repository: std::sync::Arc::new(profile_repository), + profile_repository, auth_service: std::sync::Arc::new(auth_service), }; let app = test_api(state); diff --git a/backend/tests/profile_tests.rs b/backend/tests/profile_tests.rs index 16c2b3b..ca0a27b 100644 --- a/backend/tests/profile_tests.rs +++ b/backend/tests/profile_tests.rs @@ -60,6 +60,20 @@ mod github_handle_tests { }) .cloned()) } + + async fn get_login_nonce_by_wallet_address( + &self, + _address: &WalletAddress, + ) -> Result, Box> { + Ok(Some(1)) + } + + async fn increment_login_nonce( + &self, + _address: &WalletAddress, + ) -> Result<(), Box> { + Ok(()) + } } #[tokio::test] @@ -72,6 +86,7 @@ mod github_handle_tests { description: None, avatar_url: None, github_login: None, + login_nonce: 1, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; @@ -102,6 +117,7 @@ mod github_handle_tests { description: None, avatar_url: None, github_login: None, + login_nonce: 1, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; @@ -133,6 +149,7 @@ mod github_handle_tests { description: None, avatar_url: None, github_login: Some("Alice".into()), + login_nonce: 1, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; @@ -143,6 +160,7 @@ mod github_handle_tests { description: None, avatar_url: None, github_login: None, + login_nonce: 1, created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(), }; From 6dfbb4490b428428c34bec0017f78f1e7d82e126 Mon Sep 17 00:00:00 2001 From: tusharshah21 Date: Fri, 10 Oct 2025 11:29:46 +0000 Subject: [PATCH 2/5] fixed the issue with the suggestion --- backend/src/application/commands/mod.rs | 1 - backend/src/application/mod.rs | 1 + .../{commands => queries}/get_login_nonce.rs | 2 +- backend/src/application/queries/mod.rs | 1 + .../ethereum_address_verification_service.rs | 13 ++++++------- backend/src/presentation/handlers.rs | 10 +++------- backend/tests/integration_github_handle.rs | 6 +++--- 7 files changed, 15 insertions(+), 19 deletions(-) rename backend/src/application/{commands => queries}/get_login_nonce.rs (99%) create mode 100644 backend/src/application/queries/mod.rs diff --git a/backend/src/application/commands/mod.rs b/backend/src/application/commands/mod.rs index 29342de..ac3cc0c 100644 --- a/backend/src/application/commands/mod.rs +++ b/backend/src/application/commands/mod.rs @@ -1,5 +1,4 @@ pub mod create_profile; pub mod get_all_profiles; -pub mod get_login_nonce; pub mod get_profile; pub mod update_profile; diff --git a/backend/src/application/mod.rs b/backend/src/application/mod.rs index 31002c8..fd62725 100644 --- a/backend/src/application/mod.rs +++ b/backend/src/application/mod.rs @@ -1,2 +1,3 @@ pub mod commands; pub mod dtos; +pub mod queries; diff --git a/backend/src/application/commands/get_login_nonce.rs b/backend/src/application/queries/get_login_nonce.rs similarity index 99% rename from backend/src/application/commands/get_login_nonce.rs rename to backend/src/application/queries/get_login_nonce.rs index 5e33796..2921c8f 100644 --- a/backend/src/application/commands/get_login_nonce.rs +++ b/backend/src/application/queries/get_login_nonce.rs @@ -7,7 +7,7 @@ pub async fn get_login_nonce( address: String, ) -> Result { let wallet_address = WalletAddress(address); - + match profile_repository .get_login_nonce_by_wallet_address(&wallet_address) .await diff --git a/backend/src/application/queries/mod.rs b/backend/src/application/queries/mod.rs new file mode 100644 index 0000000..7e0b400 --- /dev/null +++ b/backend/src/application/queries/mod.rs @@ -0,0 +1 @@ +pub mod get_login_nonce; diff --git a/backend/src/infrastructure/services/ethereum_address_verification_service.rs b/backend/src/infrastructure/services/ethereum_address_verification_service.rs index 35d740c..970dddf 100644 --- a/backend/src/infrastructure/services/ethereum_address_verification_service.rs +++ b/backend/src/infrastructure/services/ethereum_address_verification_service.rs @@ -14,9 +14,7 @@ pub struct EthereumAddressVerificationService { impl EthereumAddressVerificationService { pub fn new(profile_repository: Arc) -> Self { - Self { - profile_repository, - } + Self { profile_repository } } } @@ -28,7 +26,10 @@ impl AuthService for EthereumAddressVerificationService { signature: &str, ) -> Result, Box> { // Create the message with the nonce - let message = format!("Sign this message to authenticate with The Guild.\n\nNonce: {}", challenge.nonce); + let message = format!( + "Sign this message to authenticate with The Guild.\n\nNonce: {}", + challenge.nonce + ); // EIP-191 prefix + keccak256 let msg_hash = hash_message(message); @@ -47,9 +48,7 @@ impl AuthService for EthereumAddressVerificationService { .increment_login_nonce(&wallet_address) .await?; - Ok(Some(AuthResult { - wallet_address, - })) + Ok(Some(AuthResult { wallet_address })) } else { Ok(None) } diff --git a/backend/src/presentation/handlers.rs b/backend/src/presentation/handlers.rs index c02fd9b..dd9f351 100644 --- a/backend/src/presentation/handlers.rs +++ b/backend/src/presentation/handlers.rs @@ -9,10 +9,10 @@ use crate::{ application::{ commands::{ create_profile::create_profile, get_all_profiles::get_all_profiles, - get_login_nonce::get_login_nonce, get_profile::get_profile, - update_profile::update_profile, + get_profile::get_profile, update_profile::update_profile, }, dtos::{CreateProfileRequest, NonceResponse, ProfileResponse, UpdateProfileRequest}, + queries::get_login_nonce::get_login_nonce, }, domain::value_objects::WalletAddress, }; @@ -83,11 +83,7 @@ pub async fn get_nonce_handler( Path(address): Path, ) -> impl IntoResponse { match get_login_nonce(state.profile_repository, address.clone()).await { - Ok(nonce) => Json(NonceResponse { - nonce, - address, - }) - .into_response(), + Ok(nonce) => Json(NonceResponse { nonce, address }).into_response(), Err(e) => (StatusCode::NOT_FOUND, Json(serde_json::json!({"error": e}))).into_response(), } } diff --git a/backend/tests/integration_github_handle.rs b/backend/tests/integration_github_handle.rs index 4c00b44..fa2723c 100644 --- a/backend/tests/integration_github_handle.rs +++ b/backend/tests/integration_github_handle.rs @@ -14,7 +14,7 @@ async fn valid_github_handle_works() { let addr = listener.local_addr().unwrap(); let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); let profile_repository = std::sync::Arc::new( - guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()) + guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()), ); let auth_service = guild_backend::infrastructure::services::ethereum_address_verification_service::EthereumAddressVerificationService::new(profile_repository.clone()); let state = AppState { @@ -82,7 +82,7 @@ async fn invalid_format_rejected() { let addr = listener.local_addr().unwrap(); let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); let profile_repository = std::sync::Arc::new( - guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()) + guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()), ); let auth_service = guild_backend::infrastructure::services::ethereum_address_verification_service::EthereumAddressVerificationService::new(profile_repository.clone()); let state = AppState { @@ -157,7 +157,7 @@ async fn conflict_case_insensitive() { let addr = listener.local_addr().unwrap(); let pool = sqlx::PgPool::connect(&database_url).await.unwrap(); let profile_repository = std::sync::Arc::new( - guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()) + guild_backend::infrastructure::repositories::PostgresProfileRepository::new(pool.clone()), ); let auth_service = guild_backend::infrastructure::services::ethereum_address_verification_service::EthereumAddressVerificationService::new(profile_repository.clone()); let state = AppState { From c8e7dea80b147998c1a1ced7830d973a9a8379b4 Mon Sep 17 00:00:00 2001 From: tusharshah21 Date: Fri, 10 Oct 2025 18:22:11 +0000 Subject: [PATCH 3/5] fixed the structure as suggested --- ...0dbd5eed42fa868487c84ebef42dfcf695e9ce42725.json} | 12 +++--------- ...59c6815de444a5c6aadc1a9950d9d71f49f52dee768.json} | 12 +++--------- ...590574fa950a74fa68daabb48c80a0a7754e4066987.json} | 12 +++--------- backend/src/application/commands/mod.rs | 2 -- .../{commands => queries}/get_all_profiles.rs | 0 .../application/{commands => queries}/get_profile.rs | 0 backend/src/application/queries/mod.rs | 2 ++ .../repositories/postgres_profile_repository.rs | 12 ++++++------ backend/src/presentation/handlers.rs | 10 +++++----- 9 files changed, 22 insertions(+), 40 deletions(-) rename backend/.sqlx/{query-d23893061ef0e487ff20489fc5f4640e73d70dde72e7412ca265b30db5ac4249.json => query-177358fec702a5f78c1ff0dbd5eed42fa868487c84ebef42dfcf695e9ce42725.json} (75%) rename backend/.sqlx/{query-5b21241db59e84080d36c67abb309e28f8b95cedbd9da3b5a7f2b5ced0505462.json => query-b521c6c7f362753693d7059c6815de444a5c6aadc1a9950d9d71f49f52dee768.json} (77%) rename backend/.sqlx/{query-21d41f830b745d6af6e1319ee36ef0bff9dcf5f133ecb58bf2706104e55d3490.json => query-fd6f338fcae9c81fbf1d7590574fa950a74fa68daabb48c80a0a7754e4066987.json} (74%) rename backend/src/application/{commands => queries}/get_all_profiles.rs (100%) rename backend/src/application/{commands => queries}/get_profile.rs (100%) diff --git a/backend/.sqlx/query-d23893061ef0e487ff20489fc5f4640e73d70dde72e7412ca265b30db5ac4249.json b/backend/.sqlx/query-177358fec702a5f78c1ff0dbd5eed42fa868487c84ebef42dfcf695e9ce42725.json similarity index 75% rename from backend/.sqlx/query-d23893061ef0e487ff20489fc5f4640e73d70dde72e7412ca265b30db5ac4249.json rename to backend/.sqlx/query-177358fec702a5f78c1ff0dbd5eed42fa868487c84ebef42dfcf695e9ce42725.json index 3329f2f..020138d 100644 --- a/backend/.sqlx/query-d23893061ef0e487ff20489fc5f4640e73d70dde72e7412ca265b30db5ac4249.json +++ b/backend/.sqlx/query-177358fec702a5f78c1ff0dbd5eed42fa868487c84ebef42dfcf695e9ce42725.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at\n FROM profiles\n WHERE address = $1\n ", + "query": "\n SELECT address, name, description, avatar_url, github_login, created_at, updated_at\n FROM profiles\n WHERE LOWER(github_login) = LOWER($1)\n ", "describe": { "columns": [ { @@ -30,16 +30,11 @@ }, { "ordinal": 5, - "name": "login_nonce", - "type_info": "Int8" - }, - { - "ordinal": 6, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 7, + "ordinal": 6, "name": "updated_at", "type_info": "Timestamptz" } @@ -55,10 +50,9 @@ true, true, true, - false, true, true ] }, - "hash": "d23893061ef0e487ff20489fc5f4640e73d70dde72e7412ca265b30db5ac4249" + "hash": "177358fec702a5f78c1ff0dbd5eed42fa868487c84ebef42dfcf695e9ce42725" } diff --git a/backend/.sqlx/query-5b21241db59e84080d36c67abb309e28f8b95cedbd9da3b5a7f2b5ced0505462.json b/backend/.sqlx/query-b521c6c7f362753693d7059c6815de444a5c6aadc1a9950d9d71f49f52dee768.json similarity index 77% rename from backend/.sqlx/query-5b21241db59e84080d36c67abb309e28f8b95cedbd9da3b5a7f2b5ced0505462.json rename to backend/.sqlx/query-b521c6c7f362753693d7059c6815de444a5c6aadc1a9950d9d71f49f52dee768.json index 076074a..0e00234 100644 --- a/backend/.sqlx/query-5b21241db59e84080d36c67abb309e28f8b95cedbd9da3b5a7f2b5ced0505462.json +++ b/backend/.sqlx/query-b521c6c7f362753693d7059c6815de444a5c6aadc1a9950d9d71f49f52dee768.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at\n FROM profiles\n ", + "query": "\n SELECT address, name, description, avatar_url, github_login, created_at, updated_at\n FROM profiles\n ", "describe": { "columns": [ { @@ -30,16 +30,11 @@ }, { "ordinal": 5, - "name": "login_nonce", - "type_info": "Int8" - }, - { - "ordinal": 6, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 7, + "ordinal": 6, "name": "updated_at", "type_info": "Timestamptz" } @@ -53,10 +48,9 @@ true, true, true, - false, true, true ] }, - "hash": "5b21241db59e84080d36c67abb309e28f8b95cedbd9da3b5a7f2b5ced0505462" + "hash": "b521c6c7f362753693d7059c6815de444a5c6aadc1a9950d9d71f49f52dee768" } diff --git a/backend/.sqlx/query-21d41f830b745d6af6e1319ee36ef0bff9dcf5f133ecb58bf2706104e55d3490.json b/backend/.sqlx/query-fd6f338fcae9c81fbf1d7590574fa950a74fa68daabb48c80a0a7754e4066987.json similarity index 74% rename from backend/.sqlx/query-21d41f830b745d6af6e1319ee36ef0bff9dcf5f133ecb58bf2706104e55d3490.json rename to backend/.sqlx/query-fd6f338fcae9c81fbf1d7590574fa950a74fa68daabb48c80a0a7754e4066987.json index bb66029..7e5ec3a 100644 --- a/backend/.sqlx/query-21d41f830b745d6af6e1319ee36ef0bff9dcf5f133ecb58bf2706104e55d3490.json +++ b/backend/.sqlx/query-fd6f338fcae9c81fbf1d7590574fa950a74fa68daabb48c80a0a7754e4066987.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at\n FROM profiles\n WHERE LOWER(github_login) = LOWER($1)\n ", + "query": "\n SELECT address, name, description, avatar_url, github_login, created_at, updated_at\n FROM profiles\n WHERE address = $1\n ", "describe": { "columns": [ { @@ -30,16 +30,11 @@ }, { "ordinal": 5, - "name": "login_nonce", - "type_info": "Int8" - }, - { - "ordinal": 6, "name": "created_at", "type_info": "Timestamptz" }, { - "ordinal": 7, + "ordinal": 6, "name": "updated_at", "type_info": "Timestamptz" } @@ -55,10 +50,9 @@ true, true, true, - false, true, true ] }, - "hash": "21d41f830b745d6af6e1319ee36ef0bff9dcf5f133ecb58bf2706104e55d3490" + "hash": "fd6f338fcae9c81fbf1d7590574fa950a74fa68daabb48c80a0a7754e4066987" } diff --git a/backend/src/application/commands/mod.rs b/backend/src/application/commands/mod.rs index ac3cc0c..d5e1704 100644 --- a/backend/src/application/commands/mod.rs +++ b/backend/src/application/commands/mod.rs @@ -1,4 +1,2 @@ pub mod create_profile; -pub mod get_all_profiles; -pub mod get_profile; pub mod update_profile; diff --git a/backend/src/application/commands/get_all_profiles.rs b/backend/src/application/queries/get_all_profiles.rs similarity index 100% rename from backend/src/application/commands/get_all_profiles.rs rename to backend/src/application/queries/get_all_profiles.rs diff --git a/backend/src/application/commands/get_profile.rs b/backend/src/application/queries/get_profile.rs similarity index 100% rename from backend/src/application/commands/get_profile.rs rename to backend/src/application/queries/get_profile.rs diff --git a/backend/src/application/queries/mod.rs b/backend/src/application/queries/mod.rs index 7e0b400..298cba8 100644 --- a/backend/src/application/queries/mod.rs +++ b/backend/src/application/queries/mod.rs @@ -1 +1,3 @@ +pub mod get_all_profiles; pub mod get_login_nonce; +pub mod get_profile; diff --git a/backend/src/infrastructure/repositories/postgres_profile_repository.rs b/backend/src/infrastructure/repositories/postgres_profile_repository.rs index e951f8e..98b5159 100644 --- a/backend/src/infrastructure/repositories/postgres_profile_repository.rs +++ b/backend/src/infrastructure/repositories/postgres_profile_repository.rs @@ -24,7 +24,7 @@ impl ProfileRepository for PostgresProfileRepository { ) -> Result, Box> { let row = sqlx::query!( r#" - SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at + SELECT address, name, description, avatar_url, github_login, created_at, updated_at FROM profiles WHERE address = $1 "#, @@ -40,7 +40,7 @@ impl ProfileRepository for PostgresProfileRepository { description: r.description, avatar_url: r.avatar_url, github_login: r.github_login, - login_nonce: r.login_nonce, + login_nonce: 0, // Not needed for regular profile queries created_at: r.created_at.unwrap(), updated_at: r.updated_at.unwrap(), })) @@ -49,7 +49,7 @@ impl ProfileRepository for PostgresProfileRepository { async fn find_all(&self) -> Result, Box> { let rows = sqlx::query!( r#" - SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at + SELECT address, name, description, avatar_url, github_login, created_at, updated_at FROM profiles "#, ) @@ -65,7 +65,7 @@ impl ProfileRepository for PostgresProfileRepository { description: r.description, avatar_url: r.avatar_url, github_login: r.github_login, - login_nonce: r.login_nonce, + login_nonce: 0, // Not needed for regular profile queries created_at: r.created_at.unwrap(), updated_at: r.updated_at.unwrap(), }) @@ -136,7 +136,7 @@ impl ProfileRepository for PostgresProfileRepository { ) -> Result, Box> { let row = sqlx::query!( r#" - SELECT address, name, description, avatar_url, github_login, login_nonce, created_at, updated_at + SELECT address, name, description, avatar_url, github_login, created_at, updated_at FROM profiles WHERE LOWER(github_login) = LOWER($1) "#, @@ -152,7 +152,7 @@ impl ProfileRepository for PostgresProfileRepository { description: r.description, avatar_url: r.avatar_url, github_login: r.github_login, - login_nonce: r.login_nonce, + login_nonce: 0, // Not needed for regular profile queries created_at: r.created_at.unwrap(), updated_at: r.updated_at.unwrap(), })) diff --git a/backend/src/presentation/handlers.rs b/backend/src/presentation/handlers.rs index dd9f351..79ebd72 100644 --- a/backend/src/presentation/handlers.rs +++ b/backend/src/presentation/handlers.rs @@ -7,12 +7,12 @@ use axum::{ use crate::{ application::{ - commands::{ - create_profile::create_profile, get_all_profiles::get_all_profiles, - get_profile::get_profile, update_profile::update_profile, - }, + commands::{create_profile::create_profile, update_profile::update_profile}, dtos::{CreateProfileRequest, NonceResponse, ProfileResponse, UpdateProfileRequest}, - queries::get_login_nonce::get_login_nonce, + queries::{ + get_all_profiles::get_all_profiles, get_login_nonce::get_login_nonce, + get_profile::get_profile, + }, }, domain::value_objects::WalletAddress, }; From d7d288d2981f5ec8ed60c063ecbb2e922d901bf9 Mon Sep 17 00:00:00 2001 From: tusharshah21 Date: Mon, 13 Oct 2025 05:45:12 +0000 Subject: [PATCH 4/5] implemented the siwe auth on the frontend --- .../application/queries/get_login_nonce.rs | 2 +- .../postgres_profile_repository.rs | 2 +- backend/src/presentation/api.rs | 1 + backend/src/presentation/middlewares.rs | 2 +- frontend/.astro/data-store.json | 2 +- .../action-buttons/CreateProfileDialog.tsx | 45 ++++++++++++++++--- .../action-buttons/DeleteProfileDialog.tsx | 35 +++++++++++++-- .../action-buttons/EditProfileDialog.tsx | 40 +++++++++++++++-- .../profiles/list/ProfileCardSkeleton.tsx | 0 .../src/hooks/profiles/use-create-profile.ts | 15 ++++--- .../src/hooks/profiles/use-delete-profile.ts | 21 ++++----- frontend/src/hooks/profiles/use-get-nonce.ts | 41 +++++++++++++++++ .../src/hooks/profiles/use-update-profile.ts | 23 +++++----- frontend/src/lib/types/api.d.ts | 10 ++--- frontend/src/lib/utils/siwe.ts | 3 ++ 15 files changed, 192 insertions(+), 50 deletions(-) create mode 100644 frontend/src/components/profiles/list/ProfileCardSkeleton.tsx create mode 100644 frontend/src/hooks/profiles/use-get-nonce.ts create mode 100644 frontend/src/lib/utils/siwe.ts diff --git a/backend/src/application/queries/get_login_nonce.rs b/backend/src/application/queries/get_login_nonce.rs index 2921c8f..6494d03 100644 --- a/backend/src/application/queries/get_login_nonce.rs +++ b/backend/src/application/queries/get_login_nonce.rs @@ -13,7 +13,7 @@ pub async fn get_login_nonce( .await { Ok(Some(nonce)) => Ok(nonce), - Ok(None) => Err("Profile not found".to_string()), + Ok(None) => Ok(1), // Return default nonce for new addresses Err(e) => Err(format!("Error fetching nonce: {}", e)), } } diff --git a/backend/src/infrastructure/repositories/postgres_profile_repository.rs b/backend/src/infrastructure/repositories/postgres_profile_repository.rs index 98b5159..3eb691d 100644 --- a/backend/src/infrastructure/repositories/postgres_profile_repository.rs +++ b/backend/src/infrastructure/repositories/postgres_profile_repository.rs @@ -184,7 +184,7 @@ impl ProfileRepository for PostgresProfileRepository { sqlx::query!( r#" UPDATE profiles - SET login_nonce = login_nonce + 1 + SET login_nonce = login_nonce + 1, updated_at = NOW() WHERE address = $1 "#, address.as_str() diff --git a/backend/src/presentation/api.rs b/backend/src/presentation/api.rs index b712dfd..587a993 100644 --- a/backend/src/presentation/api.rs +++ b/backend/src/presentation/api.rs @@ -37,6 +37,7 @@ pub async fn create_app(pool: sqlx::PgPool) -> Router { let protected_routes = Router::new() .route("/profiles", post(create_profile_handler)) + .route("/profiles/", post(create_profile_handler)) .route("/profiles/:address", put(update_profile_handler)) .route("/profiles/:address", delete(delete_profile_handler)) .with_state(state.clone()); diff --git a/backend/src/presentation/middlewares.rs b/backend/src/presentation/middlewares.rs index ab07ee9..8cfd10b 100644 --- a/backend/src/presentation/middlewares.rs +++ b/backend/src/presentation/middlewares.rs @@ -50,7 +50,7 @@ pub async fn eth_auth_layer( .get_login_nonce_by_wallet_address(&wallet_address) .await .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)? - .ok_or(StatusCode::UNAUTHORIZED)?; // Profile must exist + .unwrap_or(1); // Use default nonce if profile doesn't exist let result = state .auth_service diff --git a/frontend/.astro/data-store.json b/frontend/.astro/data-store.json index 9e8a7dc..168d6e6 100644 --- a/frontend/.astro/data-store.json +++ b/frontend/.astro/data-store.json @@ -1 +1 @@ -[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.14.1","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\",\"entrypoint\":\"astro/assets/endpoint/node\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false},\"legacy\":{\"collections\":false},\"session\":{\"driver\":\"fs-lite\",\"options\":{\"base\":\"/Users/antoineestienne/GithubRepositories/TheGuildGenesis/frontend/node_modules/.astro/sessions\"}}}"] \ No newline at end of file +[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.14.4","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":false,\"port\":4321,\"streaming\":true,\"allowedHosts\":[]},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\",\"entrypoint\":\"astro/assets/endpoint/dev\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[],\"responsiveStyles\":false},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":{\"type\":\"shiki\",\"excludeLangs\":[\"math\"]},\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true,\"allowedDomains\":[]},\"env\":{\"schema\":{},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"headingIdCompat\":false,\"preserveScriptOrder\":false,\"liveContentCollections\":false,\"csp\":false,\"staticImportMetaEnv\":false,\"chromeDevtoolsWorkspace\":false,\"failOnPrerenderConflict\":false},\"legacy\":{\"collections\":false},\"session\":{\"driver\":\"fs-lite\",\"options\":{\"base\":\"/home/tushar/open/TheGuildGenesis/frontend/node_modules/.astro/sessions\"}}}"] \ No newline at end of file diff --git a/frontend/src/components/profiles/action-buttons/CreateProfileDialog.tsx b/frontend/src/components/profiles/action-buttons/CreateProfileDialog.tsx index 390266a..253731d 100644 --- a/frontend/src/components/profiles/action-buttons/CreateProfileDialog.tsx +++ b/frontend/src/components/profiles/action-buttons/CreateProfileDialog.tsx @@ -12,6 +12,7 @@ import { useState } from "react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { useCreateProfile } from "@/hooks/profiles/use-create-profile"; +import { useQueryClient } from "@tanstack/react-query"; import { Form, FormControl, @@ -23,12 +24,13 @@ import { import { useForm } from "react-hook-form"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useQueryClient } from "@tanstack/react-query"; +import { useGetNonce } from "@/hooks/profiles/use-get-nonce"; +import { generateSiweMessage } from "@/lib/utils/siwe"; +import { useAccount, useSignMessage } from "wagmi"; const formSchema = z.object({ name: z.string().min(2, { message: "Name must be at least 2 characters." }), description: z.string().optional(), - siweMessage: z.string().min(1, { message: "Message to sign is required." }), }); type FormValues = z.infer; @@ -37,19 +39,31 @@ export function CreateProfileButton() { const [open, setOpen] = useState(false); const createProfile = useCreateProfile(); const queryClient = useQueryClient(); + const { address } = useAccount(); + const { signMessageAsync } = useSignMessage(); + const { data: nonceData, isLoading: isLoadingNonce } = useGetNonce(address); + + const siweMessage = nonceData ? generateSiweMessage(nonceData) : ""; const form = useForm({ resolver: zodResolver(formSchema), - defaultValues: { name: "", description: "", siweMessage: "LOGIN_NONCE" }, + defaultValues: { name: "", description: "" }, }); const onSubmit = async (values: FormValues) => { + if (!siweMessage) { + throw new Error("SIWE message not available"); + } + + // Sign the SIWE message + const signature = await signMessageAsync({ message: siweMessage }); + await createProfile.mutateAsync({ input: { name: values.name, description: values.description || "", - siweMessage: "LOGIN_NONCE", }, + signature, }); await queryClient.invalidateQueries({ queryKey: ["profiles"] }); setOpen(false); @@ -102,14 +116,33 @@ export function CreateProfileButton() { )} /> + {siweMessage && ( +
+ Message to Sign +
+ {siweMessage} +
+

+ This message will be signed with your wallet to authenticate your profile creation. +

+
+ )}
-
{createProfile.isError ? ( diff --git a/frontend/src/components/profiles/action-buttons/DeleteProfileDialog.tsx b/frontend/src/components/profiles/action-buttons/DeleteProfileDialog.tsx index b370333..10854c0 100644 --- a/frontend/src/components/profiles/action-buttons/DeleteProfileDialog.tsx +++ b/frontend/src/components/profiles/action-buttons/DeleteProfileDialog.tsx @@ -11,6 +11,9 @@ import { Button } from "@/components/ui/button"; import { useDeleteProfile } from "@/hooks/profiles/use-delete-profile"; import { useQueryClient } from "@tanstack/react-query"; import { useState } from "react"; +import { useGetNonce } from "@/hooks/profiles/use-get-nonce"; +import { generateSiweMessage } from "@/lib/utils/siwe"; +import { useAccount, useSignMessage } from "wagmi"; interface DeleteProfileDialogProps { children: React.ReactNode; @@ -20,9 +23,19 @@ export function DeleteProfileDialog({ children }: DeleteProfileDialogProps) { const [open, setOpen] = useState(false); const deleteProfile = useDeleteProfile(); const queryClient = useQueryClient(); + const { address } = useAccount(); + const { signMessageAsync } = useSignMessage(); + const { data: nonceData, isLoading: isLoadingNonce } = useGetNonce(address); + + const siweMessage = nonceData ? generateSiweMessage(nonceData) : ""; const onConfirm = async () => { - await deleteProfile.mutateAsync({ input: { siweMessage: "LOGIN_NONCE" } }); + if (!siweMessage) { + throw new Error("SIWE message not available"); + } + + const signature = await signMessageAsync({ message: siweMessage }); + await deleteProfile.mutateAsync({ signature }); await queryClient.invalidateQueries({ queryKey: ["profiles"] }); setOpen(false); }; @@ -38,6 +51,17 @@ export function DeleteProfileDialog({ children }: DeleteProfileDialogProps) { profile. + {siweMessage && ( +
+ +
+ {siweMessage} +
+

+ This message will be signed with your wallet to authenticate profile deletion. +

+
+ )}
{deleteProfile.isError ? ( diff --git a/frontend/src/components/profiles/action-buttons/EditProfileDialog.tsx b/frontend/src/components/profiles/action-buttons/EditProfileDialog.tsx index e9b73a3..772eb0c 100644 --- a/frontend/src/components/profiles/action-buttons/EditProfileDialog.tsx +++ b/frontend/src/components/profiles/action-buttons/EditProfileDialog.tsx @@ -23,6 +23,9 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useUpdateProfile } from "@/hooks/profiles/use-update-profile"; import { useQueryClient } from "@tanstack/react-query"; import { useState, useEffect } from "react"; +import { useGetNonce } from "@/hooks/profiles/use-get-nonce"; +import { generateSiweMessage } from "@/lib/utils/siwe"; +import { useAccount, useSignMessage } from "wagmi"; interface EditProfileDialogProps { address: string; @@ -52,6 +55,11 @@ export function EditProfileDialog({ const [open, setOpen] = useState(false); const updateProfile = useUpdateProfile(); const queryClient = useQueryClient(); + const { address: signerAddress } = useAccount(); + const { signMessageAsync } = useSignMessage(); + const { data: nonceData, isLoading: isLoadingNonce } = useGetNonce(signerAddress); + + const siweMessage = nonceData ? generateSiweMessage(nonceData) : ""; const form = useForm({ resolver: zodResolver(formSchema), @@ -74,14 +82,21 @@ export function EditProfileDialog({ }, [open, name, description, githubLogin, form]); const onSubmit = async (values: FormValues) => { + if (!siweMessage) { + throw new Error("SIWE message not available"); + } + try { + // Sign the SIWE message + const signature = await signMessageAsync({ message: siweMessage }); + await updateProfile.mutateAsync({ input: { name: values.name, description: values.description || "", github_login: values.githubLogin || "", - siweMessage: "LOGIN_NONCE", }, + signature, }); await queryClient.invalidateQueries({ queryKey: ["profiles"] }); setOpen(false); @@ -150,14 +165,33 @@ export function EditProfileDialog({ )} /> + {siweMessage && ( +
+ Message to Sign +
+ {siweMessage} +
+

+ This message will be signed with your wallet to authenticate your profile update. +

+
+ )}
-
{updateProfile.isError ? ( diff --git a/frontend/src/components/profiles/list/ProfileCardSkeleton.tsx b/frontend/src/components/profiles/list/ProfileCardSkeleton.tsx new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/hooks/profiles/use-create-profile.ts b/frontend/src/hooks/profiles/use-create-profile.ts index 9337087..89d4f01 100644 --- a/frontend/src/hooks/profiles/use-create-profile.ts +++ b/frontend/src/hooks/profiles/use-create-profile.ts @@ -1,7 +1,6 @@ -import { useMutation, type UseMutationResult } from "@tanstack/react-query"; +import { useMutation, type UseMutationResult, useQueryClient } from "@tanstack/react-query"; import { useAccount, useSignMessage } from "wagmi"; -import type { CreateProfileInput } from "@/lib/types/api"; -import type { CreateProfileResponse } from "@/lib/types/api"; +import type { CreateProfileInput, CreateProfileResponse } from "@/lib/types/api"; import { API_BASE_URL } from "@/lib/constants/apiConstants"; async function postCreateProfile( @@ -15,7 +14,6 @@ async function postCreateProfile( "Content-Type": "application/json", "x-eth-address": address, "x-eth-signature": signature, - "x-siwe-message": input.siweMessage, }, body: JSON.stringify(input), }); @@ -39,6 +37,7 @@ async function postCreateProfile( type MutationVariables = { input: CreateProfileInput; + signature: string; }; export function useCreateProfile(): UseMutationResult< @@ -48,15 +47,19 @@ export function useCreateProfile(): UseMutationResult< > { const { address } = useAccount(); const { signMessageAsync } = useSignMessage(); + const queryClient = useQueryClient(); return useMutation({ mutationKey: ["create-profile"], - mutationFn: async ({ input }) => { + mutationFn: async ({ input, signature }) => { if (!address) { throw new Error("Wallet not connected"); } - const signature = await signMessageAsync({ message: input.siweMessage }); return postCreateProfile(input, address, signature); }, + onSuccess: () => { + // Invalidate nonce query since it was incremented + queryClient.invalidateQueries({ queryKey: ["nonce", address] }); + }, }); } diff --git a/frontend/src/hooks/profiles/use-delete-profile.ts b/frontend/src/hooks/profiles/use-delete-profile.ts index acf8a8f..6e5c961 100644 --- a/frontend/src/hooks/profiles/use-delete-profile.ts +++ b/frontend/src/hooks/profiles/use-delete-profile.ts @@ -1,5 +1,5 @@ -import { useMutation, type UseMutationResult } from "@tanstack/react-query"; -import { useAccount, useSignMessage } from "wagmi"; +import { useMutation, type UseMutationResult, useQueryClient } from "@tanstack/react-query"; +import { useAccount } from "wagmi"; import type { DeleteProfileInput, DeleteProfileResponse, @@ -9,8 +9,7 @@ import { API_BASE_URL } from "@/lib/constants/apiConstants"; async function deleteProfile( address: string, signerAddress: string, - signature: string, - siweMessage: string + signature: string ): Promise { const response = await fetch(`${API_BASE_URL}/profiles/${address}`, { method: "DELETE", @@ -18,7 +17,6 @@ async function deleteProfile( "Content-Type": "application/json", "x-eth-address": signerAddress, "x-eth-signature": signature, - "x-siwe-message": siweMessage, }, }); @@ -39,7 +37,7 @@ async function deleteProfile( } type MutationVariables = { - input: DeleteProfileInput; + signature: string; }; export function useDeleteProfile(): UseMutationResult< @@ -48,14 +46,17 @@ export function useDeleteProfile(): UseMutationResult< MutationVariables > { const { address } = useAccount(); - const { signMessageAsync } = useSignMessage(); + const queryClient = useQueryClient(); return useMutation({ mutationKey: ["delete-profile"], - mutationFn: async ({ input }) => { + mutationFn: async ({ signature }) => { if (!address) throw new Error("Wallet not connected"); - const signature = await signMessageAsync({ message: input.siweMessage }); - return deleteProfile(address, address, signature, input.siweMessage); + return deleteProfile(address, address, signature); + }, + onSuccess: () => { + // Invalidate nonce query since it was incremented + queryClient.invalidateQueries({ queryKey: ["nonce", address] }); }, }); } diff --git a/frontend/src/hooks/profiles/use-get-nonce.ts b/frontend/src/hooks/profiles/use-get-nonce.ts new file mode 100644 index 0000000..1e5a656 --- /dev/null +++ b/frontend/src/hooks/profiles/use-get-nonce.ts @@ -0,0 +1,41 @@ +import { useQuery } from "@tanstack/react-query"; +import { API_BASE_URL } from "@/lib/constants/apiConstants"; + +async function getNonce(walletAddress: string): Promise { + const response = await fetch(`${API_BASE_URL}/auth/nonce/${walletAddress}`, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + + if (!response.ok) { + const text = await response.text().catch(() => ""); + throw new Error( + `Failed to get nonce: ${response.status} ${response.statusText}${ + text ? ` - ${text}` : "" + }` + ); + } + + const data = await response.json(); + // Convert nonce number to string for signing + return data.nonce?.toString() || data.toString(); +} + +export function useGetNonce(walletAddress: string | undefined) { + return useQuery({ + queryKey: ["nonce", walletAddress], + queryFn: () => { + if (!walletAddress) { + throw new Error("Wallet address is required"); + } + return getNonce(walletAddress); + }, + enabled: !!walletAddress, + staleTime: 0, + gcTime: 0, + refetchOnMount: true, + refetchOnWindowFocus: true, + }); +} \ No newline at end of file diff --git a/frontend/src/hooks/profiles/use-update-profile.ts b/frontend/src/hooks/profiles/use-update-profile.ts index 6ff69dd..7be91b1 100644 --- a/frontend/src/hooks/profiles/use-update-profile.ts +++ b/frontend/src/hooks/profiles/use-update-profile.ts @@ -1,5 +1,5 @@ -import { useMutation, type UseMutationResult } from "@tanstack/react-query"; -import { useAccount, useSignMessage } from "wagmi"; +import { useMutation, type UseMutationResult, useQueryClient } from "@tanstack/react-query"; +import { useAccount } from "wagmi"; import type { UpdateProfileInput, UpdateProfileResponse, @@ -8,10 +8,9 @@ import { API_BASE_URL } from "@/lib/constants/apiConstants"; async function putUpdateProfile( address: string, - body: Omit, + body: UpdateProfileInput, signerAddress: string, - signature: string, - siweMessage: string + signature: string ): Promise { const response = await fetch(`${API_BASE_URL}/profiles/${address}`, { method: "PUT", @@ -19,7 +18,6 @@ async function putUpdateProfile( "Content-Type": "application/json", "x-eth-address": signerAddress, "x-eth-signature": signature, - "x-siwe-message": siweMessage, }, body: JSON.stringify(body), }); @@ -42,6 +40,7 @@ async function putUpdateProfile( type MutationVariables = { input: UpdateProfileInput; + signature: string; }; export function useUpdateProfile(): UseMutationResult< @@ -50,15 +49,17 @@ export function useUpdateProfile(): UseMutationResult< MutationVariables > { const { address } = useAccount(); - const { signMessageAsync } = useSignMessage(); + const queryClient = useQueryClient(); return useMutation({ mutationKey: ["update-profile"], - mutationFn: async ({ input }) => { + mutationFn: async ({ input, signature }) => { if (!address) throw new Error("Wallet not connected"); - const signature = await signMessageAsync({ message: input.siweMessage }); - const { siweMessage, ...rest } = input; - return putUpdateProfile(address, rest, address, signature, siweMessage); + return putUpdateProfile(address, input, address, signature); + }, + onSuccess: () => { + // Invalidate nonce query since it was incremented + queryClient.invalidateQueries({ queryKey: ["nonce", address] }); }, }); } diff --git a/frontend/src/lib/types/api.d.ts b/frontend/src/lib/types/api.d.ts index 292c658..b7236f5 100644 --- a/frontend/src/lib/types/api.d.ts +++ b/frontend/src/lib/types/api.d.ts @@ -3,16 +3,14 @@ export type CreateProfileInput = { name: string; description?: string; avatar_url?: string; - github_login?: string; - siweMessage: string; + github_login?: string; }; export type UpdateProfileInput = { name?: string; description?: string; avatar_url?: string; - github_login?: string; - siweMessage: string; + github_login?: string; }; export type UpdateProfileResponse = unknown; @@ -20,8 +18,6 @@ export type UpdateProfileResponse = unknown; // Unknown response shape from backend; expose as unknown for consumers to refine export type CreateProfileResponse = unknown; -export type DeleteProfileInput = { - siweMessage: string; -}; +export type DeleteProfileInput = {}; export type DeleteProfileResponse = unknown; \ No newline at end of file diff --git a/frontend/src/lib/utils/siwe.ts b/frontend/src/lib/utils/siwe.ts new file mode 100644 index 0000000..4285b00 --- /dev/null +++ b/frontend/src/lib/utils/siwe.ts @@ -0,0 +1,3 @@ +export function generateSiweMessage(nonce: string): string { + return `Sign this message to authenticate with The Guild.\n\nNonce: ${nonce}`; +} \ No newline at end of file From 45202e194b291611def5d639abf5845956a5a54a Mon Sep 17 00:00:00 2001 From: tusharshah21 Date: Mon, 13 Oct 2025 05:58:51 +0000 Subject: [PATCH 5/5] fix: Update SQLX --- ...a7ea4408e50c99e9fc88b86f34a7c58b8bc14c14eaf0754b51ad.json} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename backend/.sqlx/{query-fe47a9ce9d61692552d87b31b3596ded14179f08a15e3235d3e95b95964bd2c7.json => query-8120a938d5a6a7ea4408e50c99e9fc88b86f34a7c58b8bc14c14eaf0754b51ad.json} (56%) diff --git a/backend/.sqlx/query-fe47a9ce9d61692552d87b31b3596ded14179f08a15e3235d3e95b95964bd2c7.json b/backend/.sqlx/query-8120a938d5a6a7ea4408e50c99e9fc88b86f34a7c58b8bc14c14eaf0754b51ad.json similarity index 56% rename from backend/.sqlx/query-fe47a9ce9d61692552d87b31b3596ded14179f08a15e3235d3e95b95964bd2c7.json rename to backend/.sqlx/query-8120a938d5a6a7ea4408e50c99e9fc88b86f34a7c58b8bc14c14eaf0754b51ad.json index afb34ed..7c7f78e 100644 --- a/backend/.sqlx/query-fe47a9ce9d61692552d87b31b3596ded14179f08a15e3235d3e95b95964bd2c7.json +++ b/backend/.sqlx/query-8120a938d5a6a7ea4408e50c99e9fc88b86f34a7c58b8bc14c14eaf0754b51ad.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE profiles\n SET login_nonce = login_nonce + 1\n WHERE address = $1\n ", + "query": "\n UPDATE profiles\n SET login_nonce = login_nonce + 1, updated_at = NOW()\n WHERE address = $1\n ", "describe": { "columns": [], "parameters": { @@ -10,5 +10,5 @@ }, "nullable": [] }, - "hash": "fe47a9ce9d61692552d87b31b3596ded14179f08a15e3235d3e95b95964bd2c7" + "hash": "8120a938d5a6a7ea4408e50c99e9fc88b86f34a7c58b8bc14c14eaf0754b51ad" }