Initial commit

This commit is contained in:
Tony Klink 2024-01-12 09:35:31 -06:00
commit 9fe412be11
Signed by: klink
GPG key ID: 85175567C4D19231
58 changed files with 6215 additions and 0 deletions

62
src/usernames/accounts.rs Normal file
View file

@ -0,0 +1,62 @@
use crate::noose::user::{User, UserRow};
use crate::bussy::{channels, Command, Message};
use crate::usernames::util::InvalidParameter;
use crate::utils::error::Error;
use warp::{Rejection, Reply};
use super::Context;
pub async fn create_account(user: User, context: Context) -> Result<impl Reply, Rejection> {
let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await;
let pubkey = match user.pubkey {
Some(pk) => {
use nostr::prelude::FromPkStr;
let keys = nostr::Keys::from_pk_str(&pk).unwrap();
keys.public_key().to_string()
}
None => {
return Err(warp::reject::custom(Error::bad_request(
"Pubkey is required",
)))
}
};
let name = match user.name {
Some(n) => n,
None => return Err(warp::reject::custom(Error::bad_request("Name is required"))),
};
let user_row = UserRow::new(pubkey, name, false);
let command = Command::DbReqInsertUser(user_row);
context
.pubsub
.publish(
channels::MSG_NOOSE,
Message {
source: channels::MSG_NIP05,
content: command,
},
)
.await;
if let Ok(result) = subscriber.recv().await {
match result.content {
Command::DbResOk => Ok(warp::reply::with_status(
"ACCOUNT CREATED",
warp::http::StatusCode::CREATED,
)),
Command::ServiceError(e) => Err(warp::reject::custom(e)),
// _ => Err(warp::reject::custom(InvalidParameter)),
_ => Ok(warp::reply::with_status(
"something else",
warp::http::StatusCode::INTERNAL_SERVER_ERROR,
)),
}
} else {
Err(warp::reject::custom(InvalidParameter))
}
}

65
src/usernames/dto/mod.rs Normal file
View file

@ -0,0 +1,65 @@
use std::collections::HashMap;
use crate::usernames::validators::validate_pubkey;
use crate::utils::error::Error;
use nostr::prelude::*;
use nostr::{key::XOnlyPublicKey, Keys};
use regex::Regex;
use serde::{Deserialize, Serialize};
use validator::Validate;
lazy_static! {
static ref VALID_CHARACTERS: Regex = Regex::new(r"^[a-zA-Z0-9\_]+$").unwrap();
}
#[derive(Serialize, Deserialize, Debug, Validate)]
pub struct UserBody {
#[validate(length(min = 1), regex = "VALID_CHARACTERS")]
pub name: String,
#[validate(custom(function = "validate_pubkey"))]
pub pubkey: String,
}
impl UserBody {
pub fn get_pubkey(&self) -> XOnlyPublicKey {
let keys = Keys::from_pk_str(&self.pubkey).unwrap();
keys.public_key()
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Nip05 {
names: HashMap<String, String>,
}
#[derive(Serialize, Deserialize, Debug, Validate, Clone)]
pub struct UserQuery {
#[validate(length(min = 1))]
pub user: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Validate)]
pub struct Account {
#[validate(custom(function = "validate_pubkey"))]
pub pubkey: String,
#[validate(length(min = 1), regex = "VALID_CHARACTERS")]
pub name: String,
#[validate(length(min = 1))]
pub password: String,
}
impl Account {
pub fn get_pubkey(&self) -> Result<XOnlyPublicKey, Error> {
match nostr::Keys::from_pk_str(&self.pubkey) {
Ok(pk) => Ok(pk.public_key()),
Err(e) => Err(Error::invalid_param("pubkey", self.pubkey.clone())),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, Validate)]
pub struct AccountPubkey {
#[validate(custom(function = "validate_pubkey"))]
pub pubkey: String,
}

27
src/usernames/filter.rs Normal file
View file

@ -0,0 +1,27 @@
use crate::utils::error::Error;
use validator::Validate;
use warp::{Filter, Rejection};
pub fn with_client_ip() {}
pub fn with_user_body() {}
pub fn validate_body_filter<T: serde::de::DeserializeOwned + Send + Validate + 'static>(
) -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
warp::body::json::<T>().and_then(|query: T| async move {
match query.validate() {
Ok(_) => Ok(query),
Err(e) => Err(warp::reject::custom(Error::validation_error(e))),
}
})
}
pub fn validate_query_filter<T: serde::de::DeserializeOwned + Send + Validate + 'static>(
) -> impl Filter<Extract = (T,), Error = Rejection> + Copy {
warp::query::query::<T>().and_then(|query: T| async move {
match query.validate() {
Ok(_) => Ok(query),
Err(e) => Err(warp::reject::custom(Error::validation_error(e))),
}
})
}

89
src/usernames/handler.rs Normal file
View file

@ -0,0 +1,89 @@
use crate::bussy::{channels, Command, Message};
use crate::noose::user::User;
use crate::usernames::dto::{Account, UserQuery};
use crate::utils::error::Error;
use crate::utils::structs::Context;
use serde_json::json;
use warp::{Rejection, Reply};
pub async fn get_account(
// account: Result<AccountPubkey, Error>,
account: Account,
context: Context,
) -> Result<impl Reply, Rejection> {
let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await;
let command = Command::DbReqGetAccount(account.pubkey);
context
.pubsub
.publish(
channels::MSG_NOOSE,
Message {
source: channels::MSG_NIP05,
content: command,
},
)
.await;
if let Ok(result) = subscriber.recv().await {
match result.content {
Command::DbResAccount => Ok(warp::reply::with_status(
"ACCOUNT CREATED",
warp::http::StatusCode::CREATED,
)),
Command::ServiceError(e) => Err(warp::reject::custom(e)),
_ => Err(warp::reject::custom(Error::internal_with_message(
"Unhandled message type",
))),
}
} else {
Err(warp::reject::custom(Error::internal_with_message(
"Unhandeled message type",
)))
}
}
pub async fn get_user(user_query: UserQuery, context: Context) -> Result<impl Reply, Rejection> {
let name = user_query.user;
let mut subscriber = context.pubsub.subscribe(channels::MSG_NIP05).await;
let user = User {
name: Some(name),
pubkey: None,
};
let command = Command::DbReqGetUser(user);
context
.pubsub
.publish(
channels::MSG_NOOSE,
Message {
source: channels::MSG_NIP05,
content: command,
},
)
.await;
if let Ok(message) = subscriber.recv().await {
let mut response = json!({"names": {}, "relays": {}});
match message.content {
Command::DbResUser(user) => {
response = json!({
"names": {
user.username: user.pubkey
},
"relays": {}
});
Ok(warp::reply::json(&response))
}
Command::ServiceError(e) => Ok(warp::reply::json(&response)),
_ => Err(warp::reject::custom(Error::internal_with_message(
"Unhandeled message type",
))),
}
} else {
Err(warp::reject::custom(Error::internal_with_message(
"Unhandeled message type",
)))
}
}

22
src/usernames/mod.rs Normal file
View file

@ -0,0 +1,22 @@
mod accounts;
pub mod dto;
mod filter;
mod handler;
mod routes;
mod util;
mod validators;
use super::utils::structs::Context;
use crate::utils::rejection_handler::handle_rejection;
use tokio::runtime;
use warp::Filter;
pub fn start(context: Context) {
let rt = runtime::Runtime::new().unwrap();
rt.block_on(async {
log::info!("Starting NIP-05 on http://127.0.0.1:8085");
let routes = routes::routes(context).recover(handle_rejection);
warp::serve(routes).run(([127, 0, 0, 1], 8085)).await;
})
}

57
src/usernames/routes.rs Normal file
View file

@ -0,0 +1,57 @@
use crate::noose::user::User;
use super::accounts::create_account;
use super::dto::{Account, UserQuery};
use super::filter::{validate_body_filter, validate_query_filter};
use super::handler::{get_account, get_user};
use crate::utils::filter::with_context;
use crate::utils::structs::Context;
use warp::{Filter, Rejection, Reply};
pub fn routes(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
let index = warp::path::end().map(|| warp::reply::html("<h1>SNEED!</h1>"));
index
.or(nip05_get(context.clone()))
.or(account_create(context.clone()))
}
pub fn nip05_get(context: Context) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::get()
.and(warp::path(".well-known"))
.and(warp::path("nostr.json"))
.and(validate_query_filter::<UserQuery>())
.and(with_context(context))
.and_then(get_user)
}
pub fn account_create(
context: Context,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::path("account")
.and(warp::post())
.and(validate_body_filter::<User>())
.and(with_context(context))
.and_then(create_account)
}
pub fn account_get(
context: Context,
) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
warp::path("account")
.and(warp::get())
.and(validate_body_filter::<Account>())
.and(with_context(context))
.and_then(get_account)
}
// pub fn account_update(
// context: Context,
// ) -> impl Filter<Extract = impl Reply, Error = Rejection> + Clone {
// warp::path("account")
// .and(warp::put())
// .and(warp::body::json::<Account>())
// .then(validate_body)
// .and(with_context(context))
// .and_then(update_account)
// }

3
src/usernames/util.rs Normal file
View file

@ -0,0 +1,3 @@
#[derive(Debug)]
pub struct InvalidParameter;
impl warp::reject::Reject for InvalidParameter {}

View file

@ -0,0 +1,30 @@
use super::dto::AccountPubkey;
use crate::utils::error::Error;
use nostr::prelude::FromPkStr;
use validator::{Validate, ValidationError};
pub async fn validate_account_pubkey_query(
account_pubkey: AccountPubkey,
) -> Result<AccountPubkey, Error>
where
AccountPubkey: Validate,
{
match account_pubkey.validate() {
Ok(_) => Ok(account_pubkey),
Err(e) => {
log::error!("AccountPubkey validation errors: {}", e);
Err(Error::validation_error(e))
}
}
}
pub fn validate_pubkey(value: &str) -> Result<(), ValidationError> {
if value.is_empty() {
return Err(ValidationError::new("Value is empty"));
}
match nostr::Keys::from_pk_str(value) {
Ok(_) => Ok(()),
Err(_) => Err(ValidationError::new("Unable to parse pk_str")),
}
}