add umbrella crate
This commit is contained in:
parent
d171fc723b
commit
fe04530f84
17 changed files with 247 additions and 5 deletions
|
@ -1,7 +1,10 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"core/",
|
"core/",
|
||||||
"http/"
|
"http/",
|
||||||
|
"auth/",
|
||||||
|
"captcha/",
|
||||||
|
"viendesu/",
|
||||||
]
|
]
|
||||||
resolver = "3"
|
resolver = "3"
|
||||||
|
|
||||||
|
@ -23,6 +26,9 @@ schemars = { git = "ssh://forgejo@git.viende.su:61488/VienDesu/schemars.git", fe
|
||||||
"hashbrown015"
|
"hashbrown015"
|
||||||
] }
|
] }
|
||||||
|
|
||||||
|
viendesu-captcha = { path = "captcha/" }
|
||||||
|
viendesu-auth = { path = "auth/" }
|
||||||
|
viendesu-http = { path = "http/" }
|
||||||
viendesu-core = { path = "core/" }
|
viendesu-core = { path = "core/" }
|
||||||
|
|
||||||
# TODO: upstream.
|
# TODO: upstream.
|
||||||
|
|
8
auth/Cargo.toml
Normal file
8
auth/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "viendesu-auth"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eva.workspace = true
|
||||||
|
viendesu-core.workspace = true
|
1
auth/src/lib.rs
Normal file
1
auth/src/lib.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod passwords;
|
24
auth/src/passwords.rs
Normal file
24
auth/src/passwords.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
use eva::auto_impl;
|
||||||
|
|
||||||
|
use viendesu_core::types::user::PasswordHash;
|
||||||
|
|
||||||
|
/// Passwords generation utility.
|
||||||
|
#[auto_impl(&, &mut, Arc)]
|
||||||
|
pub trait Passwords: Send + Sync {
|
||||||
|
fn verify(&self, hash: &str, cleartext: &str) -> bool;
|
||||||
|
fn make(&self, cleartext: &str) -> PasswordHash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Plaintext passwords, no hashing.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Plaintext;
|
||||||
|
|
||||||
|
impl Passwords for Plaintext {
|
||||||
|
fn verify(&self, hash: &str, cleartext: &str) -> bool {
|
||||||
|
hash == cleartext
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make(&self, cleartext: &str) -> PasswordHash {
|
||||||
|
cleartext.parse().unwrap()
|
||||||
|
}
|
||||||
|
}
|
8
captcha/Cargo.toml
Normal file
8
captcha/Cargo.toml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
[package]
|
||||||
|
name = "viendesu-captcha"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eva.workspace = true
|
||||||
|
viendesu-core.workspace = true
|
22
captcha/src/error.rs
Normal file
22
captcha/src/error.rs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
use eva::str::ToCompactString as _;
|
||||||
|
|
||||||
|
use viendesu_core::{
|
||||||
|
errors::{Aux, Generic},
|
||||||
|
mk_error,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub type CaptchaResult<T> = Result<T, CaptchaError>;
|
||||||
|
|
||||||
|
mk_error!(CaptchaError);
|
||||||
|
|
||||||
|
impl From<CaptchaError> for Aux {
|
||||||
|
fn from(value: CaptchaError) -> Self {
|
||||||
|
Self::Captcha(value.0.to_compact_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> From<CaptchaError> for Generic<S> {
|
||||||
|
fn from(value: CaptchaError) -> Self {
|
||||||
|
Self::Aux(value.into())
|
||||||
|
}
|
||||||
|
}
|
32
captcha/src/lib.rs
Normal file
32
captcha/src/lib.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use eva::fut::Fut;
|
||||||
|
|
||||||
|
use viendesu_core::{
|
||||||
|
bail,
|
||||||
|
types::captcha::Token,
|
||||||
|
world::{World, WorldMut},
|
||||||
|
};
|
||||||
|
|
||||||
|
use self::error::CaptchaResult;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
pub trait Service: Send + Sync {
|
||||||
|
fn verify<W: WorldMut>(
|
||||||
|
&self,
|
||||||
|
w: World<W>,
|
||||||
|
token: &Token,
|
||||||
|
) -> impl Fut<Output = CaptchaResult<()>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Service for bool {
|
||||||
|
async fn verify<W: WorldMut>(&self, w: World<W>, token: &Token) -> CaptchaResult<()> {
|
||||||
|
_ = w;
|
||||||
|
_ = token;
|
||||||
|
|
||||||
|
if *self {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
bail!("invalid captcha")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
email/Cargo.toml
Normal file
12
email/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "viendesu-email"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = ["smtp"]
|
||||||
|
smtp = []
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
viendesu-core.workspace = true
|
||||||
|
eva.workspace = true
|
11
email/src/error.rs
Normal file
11
email/src/error.rs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
use viendesu_core::{errors::Aux, mk_error};
|
||||||
|
|
||||||
|
pub type MailResult<O> = Result<O, MailError>;
|
||||||
|
|
||||||
|
mk_error!(MailError);
|
||||||
|
|
||||||
|
impl From<MailError> for Aux {
|
||||||
|
fn from(value: MailError) -> Self {
|
||||||
|
Self::Mail(eva::str::format_compact!("{}", value.0))
|
||||||
|
}
|
||||||
|
}
|
20
email/src/lib.rs
Normal file
20
email/src/lib.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
use eva::{data, fut::Fut};
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
pub mod mock;
|
||||||
|
pub mod smtp;
|
||||||
|
|
||||||
|
pub mod error;
|
||||||
|
|
||||||
|
#[data]
|
||||||
|
pub struct Letter<'a> {
|
||||||
|
pub subject: Cow<'a, str>,
|
||||||
|
pub contents: Cow<'a, str>,
|
||||||
|
pub content_type: Cow<'a, str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[eva::auto_impl(&, &mut, Arc, Box)]
|
||||||
|
pub trait Mailer: Send + Sync {
|
||||||
|
fn send(&self, dst: &str, letter: Letter<'_>) -> impl Fut<Output = error::MailResult<()>>;
|
||||||
|
}
|
43
email/src/mock.rs
Normal file
43
email/src/mock.rs
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
|
||||||
|
use eva::{
|
||||||
|
collections::HashMap,
|
||||||
|
str::{CompactString, ToCompactString},
|
||||||
|
};
|
||||||
|
|
||||||
|
use crate::{Letter, Mailer, error::MailResult};
|
||||||
|
|
||||||
|
pub type Mailbox = Vec<Letter<'static>>;
|
||||||
|
pub type Letters = HashMap<CompactString, Mailbox>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Default)]
|
||||||
|
pub struct Mock(Arc<Mutex<Letters>>);
|
||||||
|
|
||||||
|
impl Mock {
|
||||||
|
pub fn collect_letters(&self) -> Letters {
|
||||||
|
std::mem::take(&mut *self.0.lock().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn owned(c: Cow<'_, str>) -> Cow<'static, str> {
|
||||||
|
Cow::Owned((*c).to_owned())
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Mailer for Mock {
|
||||||
|
async fn send(&self, dst: &str, letter: Letter<'_>) -> MailResult<()> {
|
||||||
|
let dst = dst.to_compact_string();
|
||||||
|
let mut this = self.0.lock().unwrap();
|
||||||
|
let mailbox = this.entry(dst).or_default();
|
||||||
|
|
||||||
|
mailbox.push(Letter {
|
||||||
|
subject: owned(letter.subject),
|
||||||
|
contents: owned(letter.contents),
|
||||||
|
content_type: owned(letter.content_type),
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
1
email/src/smtp.rs
Normal file
1
email/src/smtp.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub struct Smtp;
|
|
@ -4,13 +4,15 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["client", "server"]
|
||||||
client = ["dep:reqwest"]
|
client = ["dep:reqwest"]
|
||||||
server = [
|
server = [
|
||||||
"dep:axum",
|
"dep:axum",
|
||||||
"dep:http-body-util",
|
"dep:http-body-util",
|
||||||
"dep:tower-http",
|
"dep:tower-http",
|
||||||
"dep:serde_urlencoded",
|
"dep:serde_urlencoded",
|
||||||
|
"eva/tokio",
|
||||||
|
"tokio/net"
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -30,4 +32,6 @@ http-body-util = { version = "0.1.3", optional = true }
|
||||||
tower-http = { optional = true, version = "0.6.6", default-features = false, features = ["limit"] }
|
tower-http = { optional = true, version = "0.6.6", default-features = false, features = ["limit"] }
|
||||||
serde_urlencoded = { version = "0.7.1", optional = true }
|
serde_urlencoded = { version = "0.7.1", optional = true }
|
||||||
|
|
||||||
|
tokio = { workspace = true, optional = true }
|
||||||
|
|
||||||
reqwest = { optional = true, version = "0.12.23", default-features = false }
|
reqwest = { optional = true, version = "0.12.23", default-features = false }
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
use eva::perfect_derive;
|
use eva::{perfect_derive, supervisor::SlaveRx};
|
||||||
|
|
||||||
|
use eyre::Context;
|
||||||
use viendesu_core::{
|
use viendesu_core::{
|
||||||
errors::AuxResult,
|
errors::AuxResult,
|
||||||
service::{Service, SessionMaker, SessionOf},
|
service::{Service, SessionMaker, SessionOf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use tokio::net;
|
||||||
|
|
||||||
pub mod config;
|
pub mod config;
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
|
@ -18,6 +21,31 @@ pub trait Types: Send + Sync + 'static {
|
||||||
type Service: Service + Clone;
|
type Service: Service + Clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn serve(
|
||||||
|
rx: SlaveRx,
|
||||||
|
config: config::Config,
|
||||||
|
router: axum::Router,
|
||||||
|
) -> eyre::Result<()> {
|
||||||
|
// TODO: use it.
|
||||||
|
_ = rx;
|
||||||
|
let config::Config { unencrypted, ssl: _ } = config;
|
||||||
|
let unencrypted = unencrypted.expect("SSL-only currently is not supported");
|
||||||
|
|
||||||
|
if !unencrypted.enable {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let listener = net::TcpListener::bind(unencrypted.listen)
|
||||||
|
.await
|
||||||
|
.wrap_err("failed to bind address")?;
|
||||||
|
|
||||||
|
axum::serve(listener, router)
|
||||||
|
.await
|
||||||
|
.wrap_err("failed to serve")?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn make_router<T: Types>(service: T::Service) -> axum::Router {
|
pub fn make_router<T: Types>(service: T::Service) -> axum::Router {
|
||||||
let scope = handler::RouterScope::root(State::<T> { service });
|
let scope = handler::RouterScope::root(State::<T> { service });
|
||||||
routes::make(scope).into_axum()
|
routes::make(scope).into_axum()
|
||||||
|
|
|
@ -52,8 +52,8 @@ pub fn ok<O: IsResponse>(format: Format, ok: O) -> AxumResponse {
|
||||||
pub fn respond<O: IsResponse>(format: Format, res: O) -> AxumResponse {
|
pub fn respond<O: IsResponse>(format: Format, res: O) -> AxumResponse {
|
||||||
let status_code = res.status_code();
|
let status_code = res.status_code();
|
||||||
let headers = [
|
let headers = [
|
||||||
header!("Server" => "Kurisu-desu"),
|
header!("server" => "Kurisu-desu"),
|
||||||
header!("Content-Type" => format.mime_type()),
|
header!("content-type" => format.mime_type()),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut body = Vec::with_capacity(128);
|
let mut body = Vec::with_capacity(128);
|
||||||
|
|
17
viendesu/Cargo.toml
Normal file
17
viendesu/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "viendesu"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2024"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
default = []
|
||||||
|
|
||||||
|
tokio = ["viendesu-core/tokio"]
|
||||||
|
http-client = ["viendesu-http/client"]
|
||||||
|
http-server = ["viendesu-http/server"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
viendesu-core = { workspace = true }
|
||||||
|
viendesu-http = { workspace = true }
|
||||||
|
viendesu-auth = { workspace = true }
|
||||||
|
viendesu-captcha = { workspace = true }
|
5
viendesu/src/lib.rs
Normal file
5
viendesu/src/lib.rs
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
pub use viendesu_core::*;
|
||||||
|
|
||||||
|
pub use viendesu_auth as auth;
|
||||||
|
pub use viendesu_captcha as captcha;
|
||||||
|
pub use viendesu_http as http;
|
Loading…
Add table
Add a link
Reference in a new issue