Skip to content
Open

Auth #20

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:
uses: dtolnay/rust-toolchain@nightly
- uses: Swatinem/rust-cache@v2
- name: Build Auth
run: cargo build --release --bin auth --no-default-features --features email
run: cargo build --release --bin auth --no-default-features
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
- name: Install Rust
uses: dtolnay/rust-toolchain@nightly
- name: Build Auth
run: cargo build --release --bin auth --no-default-features --features email
run: cargo build --release --bin auth --no-default-features
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fcore"
version = "0.5.0"
version = "0.5.1-dev"
edition = "2021"
build = "build.rs"

Expand All @@ -23,7 +23,7 @@ console-subscriber = {version = "0.4", optional = true}
dashmap = "6.1.0"
defguard_wireguard_rs = {version = "0.7.2", features=["serde"], optional = true}
futures = "0.3"
hex = { version = "0.4", optional = true}
hex = { version = "0.4"}
hmac = "0.12"
openssl = { version = "0.10", features = ["vendored"] }
prost = { version = "0.13", optional = true }
Expand Down Expand Up @@ -54,7 +54,7 @@ zmq = "0.10"
parking_lot = "0.12.5"
sha2 = "0.10"
data-encoding = "2.5"
lettre = { version = "0.11", features = ["tokio1", "builder", "smtp-transport", "tokio1-native-tls"], optional = true}
lettre = { version = "0.11", features = ["tokio1", "builder", "smtp-transport", "tokio1-native-tls"]}

[build-dependencies]
tonic-build = {version = "0.12", optional = true}
Expand All @@ -71,7 +71,6 @@ default = []
debug = ["console-subscriber"]
wireguard = ["defguard_wireguard_rs"]
xray = ["prost", "prost-derive", "tonic", "tonic-build"]
email = ["lettre", "hex"]

[[bin]]
name = "node"
Expand Down
25 changes: 24 additions & 1 deletion src/bin/api/config.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
use serde::Deserialize;
use std::net::Ipv4Addr;

use fcore::{IpAddrMask, Result, Settings};
use fcore::{Env, IpAddrMask, Result, Settings, Tag};

#[derive(Clone, Debug, Deserialize)]
pub struct ServiceSettings {
pub service: ServiceConfig,
pub pg: PostgresConfig,
pub metrics: MetricsRxConfig,
pub tasks: TasksConfig,
pub smtp: SmtpConfig,
}

impl Settings for ServiceSettings {
Expand Down Expand Up @@ -49,6 +50,9 @@ pub struct ServiceConfig {
#[serde(default = "default_log_level")]
pub log_level: String,
pub updates_endpoint_zmq: String,
pub enabled_envs: Vec<Env>,
pub enabled_tags: Vec<Tag>,
pub trial_days: i64,
}

#[derive(Clone, Debug, Deserialize, Default)]
Expand All @@ -73,3 +77,22 @@ pub struct MetricsRxConfig {
pub max_points: usize,
pub retention_seconds: i64,
}

fn default_company_website() -> String {
"http://localhost:8080".to_string()
}
#[derive(Clone, Debug, Deserialize, Default)]
pub struct SmtpConfig {
pub server: String,
pub username: String,
pub password: String,
pub port: u16,
pub from: String,
pub title: String,
pub company_name: String,
pub support: String,
pub email_file: String,
pub email_sign_token: Vec<u8>,
#[serde(default = "default_company_website")]
pub company_website: String,
}
File renamed without changes.
23 changes: 21 additions & 2 deletions src/bin/api/http/filters.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use std::sync::Arc;
use warp::Filter;

use super::super::email::EmailStore;
use super::super::sync::MemSync;

use fcore::{Env, Tag};

use fcore::{
Connection, ConnectionApiOperations, ConnectionBaseOperations, IpAddrMask, MetricStorage,
NodeStorageOperations, SubscriptionOperations,
};

use super::super::sync::MemSync;

/// Provides application state filter
pub fn with_sync<T, C, S>(
mem_sync: MemSync<T, C, S>,
Expand All @@ -31,6 +34,16 @@ pub fn with_param_vec(
) -> impl Filter<Extract = (Vec<u8>,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || param.clone())
}
pub fn with_param_envs(
param: Vec<Env>,
) -> impl Filter<Extract = (Vec<Env>,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || param.clone())
}
pub fn with_param_tags(
param: Vec<Tag>,
) -> impl Filter<Extract = (Vec<Tag>,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || param.clone())
}

pub fn with_param_ipaddrmask(
param: IpAddrMask,
Expand All @@ -49,3 +62,9 @@ pub fn with_metrics(
) -> impl Filter<Extract = (Arc<MetricStorage>,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || metrics.clone())
}

pub fn with_email_store(
email_store: EmailStore,
) -> impl Filter<Extract = (EmailStore,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || email_store.clone())
}
1 change: 1 addition & 0 deletions src/bin/api/http/handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pub mod key;
pub mod metrics;
pub mod node;
pub mod subscription;
pub mod trial;

use warp::http::StatusCode;

Expand Down
194 changes: 194 additions & 0 deletions src/bin/api/http/handlers/trial.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
use chrono::{DateTime, Utc};
use std::net::{IpAddr, Ipv4Addr};

use fcore::{
http::helpers as http, http::response::Instance, utils, utils::get_uuid_last_octet_simple,
Connection, ConnectionApiOperations, ConnectionBaseOperations, ConnectionStorageApiOperations,
Env, IpAddrMask, NodeStorageOperations, Proto, Status, Subscription, SubscriptionOperations,
SubscriptionStorageOperations, Tag, Topic, WgKeys, WgParam,
};

use super::super::super::email::EmailStore;
use super::super::super::sync::{tasks::SyncOp, MemSync};
use super::super::request;

pub async fn post_trial_handler<N, C, S>(
req: request::Trial,
memory: MemSync<N, C, S>,
store: EmailStore,
wireguard_network: IpAddrMask,
system_refer_codes: Vec<String>,
envs: Vec<Env>,
protos: Vec<Tag>,
trial_days: i64,
bonus: i64,
) -> Result<impl warp::Reply, warp::Rejection>
where
N: NodeStorageOperations + Sync + Send + Clone + 'static,
C: ConnectionApiOperations
+ ConnectionBaseOperations
+ Sync
+ Send
+ Clone
+ 'static
+ From<Connection>
+ PartialEq,
Connection: From<C>,
S: SubscriptionOperations + Send + Sync + Clone + 'static + PartialEq + From<Subscription>,
{
req.validate()?;

if let Some(ref user) = req.user {
if store.check_email_hmac(user).await {
return Ok(http::bad_request("Trial already requested"));
}
}

if let Some(ref email) = req.email {
if store.check_email_hmac(email).await {
return Ok(http::bad_request("Trial already requested"));
}
}

let mut bonus_days = 0;
let ref_by = req.referred_by.clone().unwrap_or_else(|| "WEB".to_string());
let sub_id = uuid::Uuid::new_v4();

let sub_id_to_update = if let Some(ref_by_code) = req.referred_by.clone() {
let mem = memory.memory.read().await;

let is_system_code = system_refer_codes.iter().any(|c| c == &ref_by_code);
let is_user_referral = !is_system_code;

if let Some(sub) = mem.subscriptions.find_by_refer_code(&ref_by_code) {
if is_user_referral {
bonus_days = bonus;
}
Some(sub.id())
} else {
return Ok(http::bad_request("Refer code not found"));
}
} else {
None
};

if let Some(id) = sub_id_to_update {
if let Err(e) = SyncOp::add_days(&memory, &id, bonus_days).await {
return Ok(http::internal_error(&format!(
"Couldn't add bonus days: {}",
e
)));
}
}

let now = Utc::now();

let expires_at: Option<DateTime<Utc>> = Some(now + chrono::Duration::days(trial_days.into()));

let ref_code = get_uuid_last_octet_simple(&sub_id);
let sub = Subscription::new(sub_id, req.referred_by, ref_code, expires_at);

let new_sub_id = match SyncOp::add_sub(&memory, sub.clone()).await {
Ok(Status::Ok(id)) => id,
_ => return Ok(http::internal_error("Failed to add sub")),
};

for env in envs {
for p in &protos {
let proto = match p {
Tag::Wireguard => {
let mem = memory.memory.read().await;

let last_ip: Option<Ipv4Addr> = mem
.connections
.get_last_wg_addr()
.and_then(|mask| mask.as_ipv4());

let next = match last_ip {
Some(ip) => IpAddrMask::increment_ipv4(ip),
None => wireguard_network.first_peer_ip(),
};

let next = match next {
Some(ip) => ip,
None => return Ok(http::internal_error("Failed to allocate IP")),
};

if !wireguard_network.contains_ipv4(next) {
return Ok(http::internal_error("IP out of range"));
}

Proto::Wireguard {
param: WgParam {
keys: WgKeys::default(),
address: IpAddrMask {
address: IpAddr::V4(next),
cidr: 32,
},
},
}
}
Tag::Shadowsocks => {
let password = utils::generate_random_password(15);
Proto::Shadowsocks { password }
}
Tag::VlessTcpReality
| Tag::VlessGrpcReality
| Tag::VlessXhttpReality
| Tag::Vmess => Proto::Xray(p.clone()),
Tag::Hysteria2 => {
let token = uuid::Uuid::new_v4();
Proto::Hysteria2 { token }
}
Tag::Mtproto => {
let secret = utils::generate_random_password(15);
Proto::Mtproto { secret }
}
};

let conn: Connection = Connection::new(&env, Some(new_sub_id), proto, None);
let conn_id = uuid::Uuid::new_v4();
let msg = conn.as_create_message(&conn_id);
let messages = vec![msg];

match SyncOp::add_conn(&memory, &conn_id, conn.clone()).await {
Ok(Status::Ok(_)) => {
let bytes = match rkyv::to_bytes::<_, 1024>(&messages) {
Ok(b) => b,
Err(e) => {
return Ok(http::internal_error(&format!("Serialization error: {}", e)))
}
};

let topic = if conn.get_token().is_some() {
Some(Topic::Auth)
} else if conn.get_proto().is_mtproto() {
None
} else {
Some(conn.get_env().into())
};

if let Some(topic) = topic {
let _ = memory.publisher.send_binary(&topic, bytes.as_ref()).await;
}
}
_ => continue,
}
}
}

if let Some(email) = req.user {
let _ = store.save_trial_hmac(&email, &sub.id, &now, &ref_by).await;
}

if let Some(email) = req.email {
let _ = store.save_trial_hmac(&email, &sub.id, &now, &ref_by).await;
let _ = store.send_email_background(email, sub.id).await;
}

Ok(http::success_response(
"Trial activated. Check email".into(),
Some(sub.id),
Instance::None,
))
}
16 changes: 16 additions & 0 deletions src/bin/api/http/request.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,19 @@ impl ConnectionInfoRequest {
Ok(())
}
}

#[derive(Debug, Deserialize)]
pub struct Trial {
pub user: Option<String>,
pub email: Option<String>,
pub referred_by: Option<String>,
}

impl Trial {
pub fn validate(&self) -> Result<(), Error> {
if self.user.is_some() && self.email.is_some() {
return Err(Error::Custom("Only one param is allowed".to_string()));
}
Ok(())
}
}
Loading
Loading