Initial commit
This commit is contained in:
commit
9fe412be11
58 changed files with 6215 additions and 0 deletions
62
src/usernames/accounts.rs
Normal file
62
src/usernames/accounts.rs
Normal 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
65
src/usernames/dto/mod.rs
Normal 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
27
src/usernames/filter.rs
Normal 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
89
src/usernames/handler.rs
Normal 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
22
src/usernames/mod.rs
Normal 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
57
src/usernames/routes.rs
Normal 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
3
src/usernames/util.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
#[derive(Debug)]
|
||||
pub struct InvalidParameter;
|
||||
impl warp::reject::Reject for InvalidParameter {}
|
30
src/usernames/validators.rs
Normal file
30
src/usernames/validators.rs
Normal 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")),
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue