mirror of https://github.com/ospab/ostp.git
feat: unlimited free core and license protection for panel API
This commit is contained in:
parent
303515cfba
commit
99ff76d595
|
|
@ -54,6 +54,7 @@ pub struct ApiState {
|
||||||
pub dns_server: std::sync::Arc<crate::dns::DnsServer>,
|
pub dns_server: std::sync::Arc<crate::dns::DnsServer>,
|
||||||
pub audit_logs: Arc<RwLock<Vec<AuditLogEntry>>>,
|
pub audit_logs: Arc<RwLock<Vec<AuditLogEntry>>>,
|
||||||
pub router: std::sync::Arc<crate::router::Router>,
|
pub router: std::sync::Arc<crate::router::Router>,
|
||||||
|
pub is_licensed: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
|
@ -208,7 +209,8 @@ pub fn create_api_router(state: ApiState) -> Router {
|
||||||
.delete(handle_clear_audit),
|
.delete(handle_clear_audit),
|
||||||
)
|
)
|
||||||
.route("/users/bulk", post(handle_bulk_create_users))
|
.route("/users/bulk", post(handle_bulk_create_users))
|
||||||
.route("/router/rules", get(handle_get_rules).put(handle_put_rules));
|
.route("/router/rules", get(handle_get_rules).put(handle_put_rules))
|
||||||
|
.layer(axum::middleware::from_fn_with_state(state.clone(), license_middleware));
|
||||||
|
|
||||||
let webpath = state.webpath.clone();
|
let webpath = state.webpath.clone();
|
||||||
let webpath = webpath.trim_matches('/');
|
let webpath = webpath.trim_matches('/');
|
||||||
|
|
@ -236,6 +238,25 @@ pub fn create_api_router(state: ApiState) -> Router {
|
||||||
.layer(cors)
|
.layer(cors)
|
||||||
.with_state(state)
|
.with_state(state)
|
||||||
}
|
}
|
||||||
|
async fn license_middleware(
|
||||||
|
axum::extract::State(state): axum::extract::State<ApiState>,
|
||||||
|
req: axum::extract::Request,
|
||||||
|
next: axum::middleware::Next,
|
||||||
|
) -> axum::response::Response {
|
||||||
|
if state.is_licensed {
|
||||||
|
return next.run(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = req.uri().path();
|
||||||
|
// Allow read-only access to users for relay, and server status
|
||||||
|
if (path == "/server/status" && req.method() == axum::http::Method::GET) ||
|
||||||
|
(path == "/users" && req.method() == axum::http::Method::GET)
|
||||||
|
{
|
||||||
|
return next.run(req).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
(axum::http::StatusCode::PAYMENT_REQUIRED, "This feature requires an active OSTP license. Get yours at https://ostp.ospab.lol").into_response()
|
||||||
|
}
|
||||||
|
|
||||||
/// Start the Management API server on the configured bind address.
|
/// Start the Management API server on the configured bind address.
|
||||||
pub async fn start_api_server(
|
pub async fn start_api_server(
|
||||||
|
|
@ -247,6 +268,7 @@ pub async fn start_api_server(
|
||||||
config_path: Option<std::path::PathBuf>,
|
config_path: Option<std::path::PathBuf>,
|
||||||
dns_server: std::sync::Arc<crate::dns::DnsServer>,
|
dns_server: std::sync::Arc<crate::dns::DnsServer>,
|
||||||
router: std::sync::Arc<crate::router::Router>,
|
router: std::sync::Arc<crate::router::Router>,
|
||||||
|
is_licensed: bool,
|
||||||
) {
|
) {
|
||||||
let state = ApiState {
|
let state = ApiState {
|
||||||
access_keys,
|
access_keys,
|
||||||
|
|
@ -263,6 +285,7 @@ pub async fn start_api_server(
|
||||||
dns_server,
|
dns_server,
|
||||||
audit_logs: Arc::new(RwLock::new(Vec::new())),
|
audit_logs: Arc::new(RwLock::new(Vec::new())),
|
||||||
router,
|
router,
|
||||||
|
is_licensed,
|
||||||
};
|
};
|
||||||
|
|
||||||
let app = create_api_router(state);
|
let app = create_api_router(state);
|
||||||
|
|
|
||||||
|
|
@ -81,12 +81,11 @@ pub struct Dispatcher {
|
||||||
replay_cache: std::collections::HashMap<Vec<u8>, u64>,
|
replay_cache: std::collections::HashMap<Vec<u8>, u64>,
|
||||||
roaming_tokens: f64,
|
roaming_tokens: f64,
|
||||||
last_token_regen: std::time::Instant,
|
last_token_regen: std::time::Instant,
|
||||||
max_sessions: Option<usize>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl Dispatcher {
|
impl Dispatcher {
|
||||||
pub fn new(machine_config: ProtocolConfig, access_keys: Arc<RwLock<HashMap<String, crate::api::UserMeta>>>, max_sessions: Option<usize>) -> Self {
|
pub fn new(machine_config: ProtocolConfig, access_keys: Arc<RwLock<HashMap<String, crate::api::UserMeta>>>) -> Self {
|
||||||
let mut initial_stats = HashMap::new();
|
let mut initial_stats = HashMap::new();
|
||||||
for (key, meta) in access_keys.read().unwrap_or_else(|e| e.into_inner()).iter() {
|
for (key, meta) in access_keys.read().unwrap_or_else(|e| e.into_inner()).iter() {
|
||||||
initial_stats.insert(key.clone(), Arc::new(UserStats::new(meta.limit_bytes)));
|
initial_stats.insert(key.clone(), Arc::new(UserStats::new(meta.limit_bytes)));
|
||||||
|
|
@ -100,7 +99,6 @@ impl Dispatcher {
|
||||||
replay_cache: std::collections::HashMap::new(),
|
replay_cache: std::collections::HashMap::new(),
|
||||||
roaming_tokens: 50.0,
|
roaming_tokens: 50.0,
|
||||||
last_token_regen: std::time::Instant::now(),
|
last_token_regen: std::time::Instant::now(),
|
||||||
max_sessions,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -373,11 +371,6 @@ impl Dispatcher {
|
||||||
tracing::warn!("Replay cache full (100000 entries), rejecting handshake from {}", peer);
|
tracing::warn!("Replay cache full (100000 entries), rejecting handshake from {}", peer);
|
||||||
return Ok(DispatchOutcome::Unauthorized);
|
return Ok(DispatchOutcome::Unauthorized);
|
||||||
}
|
}
|
||||||
let limit = self.max_sessions.unwrap_or(30);
|
|
||||||
if self.peer_machines.len() >= limit {
|
|
||||||
tracing::warn!("drop session by {}, for more active clients buy our license here: https://ostp.ospab.lol/license", peer.ip());
|
|
||||||
return Ok(DispatchOutcome::Unauthorized);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.replay_cache.insert(payload.to_vec(), ts);
|
self.replay_cache.insert(payload.to_vec(), ts);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -117,16 +117,15 @@ pub async fn run_server(
|
||||||
mtu: 1350,
|
mtu: 1350,
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut max_sessions = Some(30);
|
let mut is_licensed = false;
|
||||||
|
|
||||||
if let Some(key) = license_key {
|
if let Some(key) = license_key {
|
||||||
let host = server_public_ip.as_deref().unwrap_or("0.0.0.0");
|
let host = server_public_ip.as_deref().unwrap_or("0.0.0.0");
|
||||||
match crate::license::verify_license(&key, host) {
|
match crate::license::verify_license(&key, host) {
|
||||||
Ok(payload) => {
|
Ok(payload) => {
|
||||||
tracing::info!("License verified successfully! Features: {:?}", payload.features);
|
tracing::info!("License verified successfully! Features: {:?}", payload.features);
|
||||||
if payload.features.contains(&"unlimited_connections".to_string()) {
|
is_licensed = true;
|
||||||
max_sessions = None;
|
|
||||||
tracing::info!("Unlimited connections enabled.");
|
|
||||||
}
|
|
||||||
if payload.features.contains(&"control_panel".to_string()) {
|
if payload.features.contains(&"control_panel".to_string()) {
|
||||||
tracing::info!("Spawning control panel child process...");
|
tracing::info!("Spawning control panel child process...");
|
||||||
|
|
||||||
|
|
@ -152,11 +151,9 @@ pub async fn run_server(
|
||||||
tracing::error!("Failed to verify license: {:?}", e);
|
tracing::error!("Failed to verify license: {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
tracing::info!("No license key provided. Free version limited to 30 sessions.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let dispatcher = Dispatcher::new(protocol_config, shared_keys.clone(), max_sessions);
|
let dispatcher = Dispatcher::new(protocol_config, shared_keys.clone());
|
||||||
|
|
||||||
// Background config hot-reloader for access keys
|
// Background config hot-reloader for access keys
|
||||||
let shared_keys_clone = shared_keys.clone();
|
let shared_keys_clone = shared_keys.clone();
|
||||||
|
|
@ -307,7 +304,7 @@ pub async fn run_server(
|
||||||
let dns_server_api = dns_server.clone();
|
let dns_server_api = dns_server.clone();
|
||||||
let router_api = router.clone();
|
let router_api = router.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
api::start_api_server(api_cfg, api_keys, api_stats, server_host, server_port, config_path_api, dns_server_api, router_api).await;
|
api::start_api_server(api_cfg, api_keys, api_stats, server_host, server_port, config_path_api, dns_server_api, router_api, is_licensed).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue