diff --git a/.gitignore b/.gitignore index 9e897c5..1a75808 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /target .env .env.production -.env.test \ No newline at end of file +.env.test +.idea \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..2d889ed --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "lldb", + "request": "launch", + "name": "Debug executable 'helios-master-backend'", + "cargo": { + "args": [ + "build", + "--bin=helios-master-backend", + "--package=helios-master-backend" + ], + "filter": { + "name": "helios-master-backend", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + }, + { + "type": "lldb", + "request": "launch", + "name": "Debug unit tests in executable 'helios-master-backend'", + "cargo": { + "args": [ + "test", + "--no-run", + "--bin=helios-master-backend", + "--package=helios-master-backend" + ], + "filter": { + "name": "helios-master-backend", + "kind": "bin" + } + }, + "args": [], + "cwd": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 37d0524..112d793 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer", "editor.formatOnSave": true - } + }, + "Prettier-SQL.SQLFlavourOverride": "postgresql" } diff --git a/Cargo.lock b/Cargo.lock index 25df2d1..b018fd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -906,6 +906,7 @@ dependencies = [ "oauth2", "once_cell", "openssl", + "rand", "rand_core", "reqwest 0.12.7", "resend-rs", @@ -2380,9 +2381,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -2489,12 +2490,12 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.5.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" dependencies = [ "async-compression", - "base64 0.21.7", + "base64 0.22.1", "bitflags 2.6.0", "bytes", "futures-core", @@ -2511,7 +2512,7 @@ dependencies = [ "pin-project-lite", "tokio", "tokio-util", - "tower 0.4.13", + "tower 0.5.0", "tower-layer", "tower-service", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 2c9266b..155c535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,14 +26,15 @@ mime = "0.3" oauth2 = "4.1" once_cell = "1.19.0" openssl = "*" +rand = "0.8" rand_core = { version = "0.6.3", features = ["std"] } reqwest = { version = "0.12", features = ["json"] } resend-rs = "0.9" serde = { version = "1", features = ["derive"] } serde_json = "1" -tokio = { version = "1.39", features = ["full"] } +tokio = { version = "1.40", features = ["full"] } tower = { version = "0.5", features = ["full"] } -tower-http = { version = "0.5.0", features = ["full"] } +tower-http = { version = "0.6", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } uuid = { version = "1.10", features = [ diff --git a/migrations/2024-08-02-114025_initial/down.sql b/migrations/2024-08-02-114025_initial/down.sql deleted file mode 100644 index ba993c1..0000000 --- a/migrations/2024-08-02-114025_initial/down.sql +++ /dev/null @@ -1,66 +0,0 @@ --- Drop triggers -DROP TRIGGER table_updated_at_trigger ON "server"; - -DROP TRIGGER table_updated_at_trigger ON "config"; - -DROP TRIGGER table_updated_at_trigger ON "user"; - -DROP TRIGGER table_updated_at_trigger ON "device"; - --- Drop function -DROP FUNCTION update_updated_at (); - --- Drop foreign key constraints -ALTER TABLE "session" -DROP CONSTRAINT "session_config_id_fkey"; - -ALTER TABLE "session" -DROP CONSTRAINT "session_device_id_fkey"; - -ALTER TABLE "device" -DROP CONSTRAINT "device_user_id_fkey"; - -ALTER TABLE "config" -DROP CONSTRAINT "config_server_id_fkey"; - --- Drop indexes -DROP INDEX "session_configId_key"; - -DROP INDEX "session_deviceId_key"; - -DROP INDEX "user_email_key"; - -DROP INDEX "config_privateKey_key"; - -DROP INDEX "server_wireguardUri_key"; - -DROP INDEX "server_backendUri_key"; - -DROP INDEX "server_publicKey_key"; - --- Drop tables -DROP TABLE "session"; - -DROP TABLE "device"; - -DROP TABLE "user"; - -DROP TABLE "config"; - -DROP TABLE "server"; - --- Drop types -DROP TYPE "UserStatus"; - -DROP TYPE "DeviceStatus"; - -DROP TYPE "OS"; - -DROP TYPE "ConfigStatus"; - -DROP TYPE "SessionStatus"; - -DROP TYPE "Country"; - --- Drop extension -DROP EXTENSION "uuid-ossp"; \ No newline at end of file diff --git a/migrations/2024-08-02-114025_initial/up.sql b/migrations/2024-08-02-114025_initial/up.sql deleted file mode 100644 index 40252f9..0000000 --- a/migrations/2024-08-02-114025_initial/up.sql +++ /dev/null @@ -1,166 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; - -CREATE TYPE "Country" AS ENUM ('UK', 'USA', 'Germany'); - -CREATE TYPE "SessionStatus" AS ENUM ('Active', 'Closed'); - -CREATE TYPE "ConfigStatus" AS ENUM ('InUse', 'NotInUse'); - -CREATE TYPE "OS" AS ENUM ( - 'Windows', - 'Linux', - 'MacOS', - 'IOS', - 'Android', - 'Unknown' -); - -CREATE TYPE "DeviceStatus" AS ENUM ('LoggedIn', 'LoggedOut'); - -CREATE TYPE "UserStatus" AS ENUM ( - 'Active', - 'Banned', - 'PermanentlyBanned', - 'Deleted' -); - -CREATE TYPE "OAuthProvider" AS ENUM ('Google', 'Discord', 'GitHub'); - -CREATE TABLE - "server" ( - "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - "public_key" TEXT NOT NULL, - "wireguard_uri" TEXT NOT NULL, - "country" "Country" NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - -CREATE TABLE - "config" ( - "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - "private_key" TEXT NOT NULL, - "user_ip" TEXT NOT NULL, - "server_id" uuid NOT NULL, - "status" "ConfigStatus" NOT NULL DEFAULT 'NotInUse', - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - -CREATE TABLE - "user" ( - "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - "email" TEXT NOT NULL, - "banned_at" TIMESTAMP(3), - "banned_till" TIMESTAMP(3), - "status" "UserStatus" NOT NULL DEFAULT 'Active', - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - -CREATE TABLE - "classic_auth" ( - "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - "user_id" uuid NOT NULL, - "password_hash" TEXT NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - -CREATE TABLE - "oauth" ( - "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - "user_id" uuid NOT NULL, - "provider" TEXT NOT NULL, - "metadata" JSONB NOT NULL, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - -CREATE TABLE - "device" ( - "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - "name" TEXT NOT NULL, - "os" "OS" NOT NULL, - "user_id" uuid NOT NULL, - "banned_at" TIMESTAMP(3), - "banned_till" TIMESTAMP(3), - "status" "DeviceStatus" NOT NULL DEFAULT 'LoggedIn', - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP - ); - -CREATE TABLE - "session" ( - "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - "status" "SessionStatus" NOT NULL DEFAULT 'Active', - "opened_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "closed_at" TIMESTAMP(3), - "device_id" uuid NOT NULL, - "config_id" uuid NOT NULL - ); - -CREATE UNIQUE INDEX "server_publicKey_key" ON "server" ("public_key"); - -CREATE UNIQUE INDEX "server_wireguardUri_key" ON "server" ("wireguard_uri"); - -CREATE INDEX "config_privateKey_key" ON "config" ("private_key"); - -CREATE UNIQUE INDEX "user_email_key" ON "user" ("email"); - -CREATE INDEX "session_deviceId_key" ON "session" ("device_id"); - -CREATE INDEX "session_configId_key" ON "session" ("config_id"); - -ALTER TABLE "config" ADD CONSTRAINT "config_server_id_fkey" FOREIGN KEY ("server_id") REFERENCES "server" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; - -ALTER TABLE "device" ADD CONSTRAINT "device_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; - -ALTER TABLE "session" ADD CONSTRAINT "session_device_id_fkey" FOREIGN KEY ("device_id") REFERENCES "device" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; - -ALTER TABLE "session" ADD CONSTRAINT "session_config_id_fkey" FOREIGN KEY ("config_id") REFERENCES "config" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; - -ALTER TABLE "classic_auth" ADD CONSTRAINT "classic_auth_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; - -ALTER TABLE "oauth" ADD CONSTRAINT "oauth_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; - -CREATE OR REPLACE FUNCTION update_updated_at() -RETURNS TRIGGER AS $$ -BEGIN - NEW.updated_at = CURRENT_TIMESTAMP; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER table_updated_at_trigger -BEFORE UPDATE ON "server" -FOR EACH ROW -EXECUTE FUNCTION update_updated_at(); - - -CREATE TRIGGER table_updated_at_trigger -BEFORE UPDATE ON "config" -FOR EACH ROW -EXECUTE FUNCTION update_updated_at(); - - -CREATE TRIGGER table_updated_at_trigger -BEFORE UPDATE ON "user" -FOR EACH ROW -EXECUTE FUNCTION update_updated_at(); - - -CREATE TRIGGER table_updated_at_trigger -BEFORE UPDATE ON "device" -FOR EACH ROW -EXECUTE FUNCTION update_updated_at(); - -CREATE TRIGGER table_updated_at_trigger -BEFORE UPDATE ON "classic_auth" -FOR EACH ROW -EXECUTE FUNCTION update_updated_at(); - -CREATE TRIGGER table_updated_at_trigger -BEFORE UPDATE ON "oauth" -FOR EACH ROW -EXECUTE FUNCTION update_updated_at(); diff --git a/migrations/2024-08-11-120853_email_confirmation/down.sql b/migrations/2024-08-11-120853_email_confirmation/down.sql deleted file mode 100644 index d4ae949..0000000 --- a/migrations/2024-08-11-120853_email_confirmation/down.sql +++ /dev/null @@ -1,9 +0,0 @@ -DROP TRIGGER table_confirmed_at_trigger ON "email_confirmation"; - -DROP FUNCTION confirmed_at (); - -DROP -ALTER TABLE "email_confirmation" -DROP CONSTRAINT "fk_email_confirmation_user_id"; - -DROP TABLE "email_confirmation"; \ No newline at end of file diff --git a/migrations/2024-08-11-120853_email_confirmation/up.sql b/migrations/2024-08-11-120853_email_confirmation/up.sql deleted file mode 100644 index e2edda1..0000000 --- a/migrations/2024-08-11-120853_email_confirmation/up.sql +++ /dev/null @@ -1,23 +0,0 @@ -CREATE TABLE - "email_confirmation" ( - "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), - "user_id" uuid NOT NULL, - "confirmed" BOOLEAN NOT NULL DEFAULT FALSE, - "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - "confirmed_at" TIMESTAMP(3) - ); - -ALTER TABLE "email_confirmation" ADD CONSTRAINT "fk_email_confirmation_user_id" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE; - -CREATE OR REPLACE FUNCTION confirmed_at() -RETURNS TRIGGER AS $$ -BEGIN - NEW.confirmed_at = CURRENT_TIMESTAMP; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; - -CREATE TRIGGER table_confirmed_at_trigger -BEFORE UPDATE ON "email_confirmation" -FOR EACH ROW -EXECUTE FUNCTION confirmed_at(); diff --git a/migrations/2024-09-28-102954_initial/down.sql b/migrations/2024-09-28-102954_initial/down.sql new file mode 100644 index 0000000..8001dda --- /dev/null +++ b/migrations/2024-09-28-102954_initial/down.sql @@ -0,0 +1,62 @@ +DROP TRIGGER IF EXISTS table_updated_at_trigger ON "user"; + +DROP TRIGGER IF EXISTS table_updated_at_trigger ON "device"; + +DROP TRIGGER IF EXISTS table_updated_at_trigger ON "classic_auth"; + +DROP TRIGGER IF EXISTS table_updated_at_trigger ON "oauth"; + +DROP TRIGGER IF EXISTS table_confirmed_at_trigger ON "email_confirmation"; + +DROP FUNCTION IF EXISTS update_updated_at (); + +DROP FUNCTION IF EXISTS confirmed_at (); + +ALTER TABLE "device" +DROP CONSTRAINT IF EXISTS "device_user_id_fkey"; + +ALTER TABLE "session" +DROP CONSTRAINT IF EXISTS "session_device_id_fkey"; + +ALTER TABLE "classic_auth" +DROP CONSTRAINT IF EXISTS "classic_auth_user_id_fkey"; + +ALTER TABLE "oauth" +DROP CONSTRAINT IF EXISTS "oauth_user_id_fkey"; + +ALTER TABLE "email_confirmation" +DROP CONSTRAINT IF EXISTS "fk_email_confirmation_user_id"; + +DROP INDEX IF EXISTS "user_email_key"; + +DROP INDEX IF EXISTS "session_device_id_key"; + +DROP INDEX IF EXISTS "session_device_id_active_status_key"; + +DROP TABLE IF EXISTS "session"; + +DROP TABLE IF EXISTS "device"; + +DROP TABLE IF EXISTS "email_confirmation"; + +DROP TABLE IF EXISTS "oauth"; + +DROP TABLE IF EXISTS "classic_auth"; + +DROP TABLE IF EXISTS "user"; + +DROP TYPE IF EXISTS "Protocol"; + +DROP TYPE IF EXISTS "OAuthProvider"; + +DROP TYPE IF EXISTS "UserStatus"; + +DROP TYPE IF EXISTS "SessionStatus"; + +DROP TYPE IF EXISTS "OS"; + +DROP TYPE IF EXISTS "DeviceStatus"; + +DROP TYPE IF EXISTS "Country"; + +DROP EXTENSION IF EXISTS "uuid-ossp"; \ No newline at end of file diff --git a/migrations/2024-09-28-102954_initial/up.sql b/migrations/2024-09-28-102954_initial/up.sql new file mode 100644 index 0000000..bf23fcd --- /dev/null +++ b/migrations/2024-09-28-102954_initial/up.sql @@ -0,0 +1,136 @@ +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; + +CREATE TYPE "Country" AS ENUM('UK', 'USA', 'Germany'); + +CREATE TYPE "DeviceStatus" AS ENUM('LoggedIn', 'LoggedOut'); + +CREATE TYPE "OS" AS ENUM('Windows', 'Linux', 'MacOS', 'IOS', 'Android', 'Unknown'); + +CREATE TYPE "SessionStatus" AS ENUM('Active', 'Closed'); + +CREATE TYPE "UserStatus" AS ENUM('Active', 'Banned', 'PermanentlyBanned', 'Deleted'); + +CREATE TYPE "OAuthProvider" AS ENUM('Google', 'Discord', 'GitHub'); + +CREATE TYPE "Protocol" AS ENUM('Vless', 'Shadowsocks'); + +CREATE TABLE + "user" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), + "email" TEXT NOT NULL, + "status" "UserStatus" NOT NULL DEFAULT 'Active', + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + +CREATE TABLE + "classic_auth" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), + "user_id" uuid NOT NULL, + "password_hash" TEXT NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + +CREATE TABLE + "oauth" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), + "user_id" uuid NOT NULL, + "provider" TEXT NOT NULL, + "metadata" JSONB NOT NULL, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + +CREATE TABLE + "email_confirmation" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), + "user_id" uuid NOT NULL, + "confirmed" BOOLEAN NOT NULL DEFAULT FALSE, + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "confirmed_at" TIMESTAMP(3) + ); + +CREATE TABLE + "device" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), + "name" TEXT NOT NULL, + "os" "OS" NOT NULL, + "user_id" uuid NOT NULL, + "status" "DeviceStatus" NOT NULL DEFAULT 'LoggedIn', + "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + +CREATE TABLE + "session" ( + "id" uuid PRIMARY KEY DEFAULT uuid_generate_v4 (), + "device_id" uuid NOT NULL, + "link" TEXT NOT NULL, + "protocol" "Protocol" NOT NULL, + "country" "Country" NOT NULL, + "status" "SessionStatus" NOT NULL DEFAULT 'Active', + "up" BIGINT, + "down" BIGINT, + "opened_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "closed_at" TIMESTAMP(3) + ); + +CREATE UNIQUE INDEX "user_email_key" ON "user" ("email"); + +CREATE INDEX "session_device_id_key" ON "session" ("device_id"); + +CREATE UNIQUE INDEX "session_device_id_active_status_key" ON "session" ("device_id") +WHERE + status = 'Active'; + +ALTER TABLE "device" +ADD CONSTRAINT "device_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +ALTER TABLE "session" +ADD CONSTRAINT "session_device_id_fkey" FOREIGN KEY ("device_id") REFERENCES "device" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +ALTER TABLE "classic_auth" +ADD CONSTRAINT "classic_auth_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +ALTER TABLE "oauth" +ADD CONSTRAINT "oauth_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +ALTER TABLE "email_confirmation" +ADD CONSTRAINT "fk_email_confirmation_user_id" FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE; + +CREATE +OR REPLACE FUNCTION confirmed_at () RETURNS TRIGGER AS $$ +BEGIN + NEW.confirmed_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER table_confirmed_at_trigger BEFORE +UPDATE ON "email_confirmation" FOR EACH ROW +EXECUTE FUNCTION confirmed_at (); + +CREATE +OR REPLACE FUNCTION update_updated_at () RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = CURRENT_TIMESTAMP; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +CREATE TRIGGER table_updated_at_trigger BEFORE +UPDATE ON "user" FOR EACH ROW +EXECUTE FUNCTION update_updated_at (); + +CREATE TRIGGER table_updated_at_trigger BEFORE +UPDATE ON "device" FOR EACH ROW +EXECUTE FUNCTION update_updated_at (); + +CREATE TRIGGER table_updated_at_trigger BEFORE +UPDATE ON "classic_auth" FOR EACH ROW +EXECUTE FUNCTION update_updated_at (); + +CREATE TRIGGER table_updated_at_trigger BEFORE +UPDATE ON "oauth" FOR EACH ROW +EXECUTE FUNCTION update_updated_at (); \ No newline at end of file diff --git a/src/agent_api/dto/agent_response.rs b/src/agent_api/dto/agent_response.rs new file mode 100644 index 0000000..bc2b65b --- /dev/null +++ b/src/agent_api/dto/agent_response.rs @@ -0,0 +1,8 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct AgentResponse { + pub success: bool, + pub msg: String, + pub obj: Option, +} diff --git a/src/agent_api/dto/client.rs b/src/agent_api/dto/client.rs new file mode 100644 index 0000000..485b597 --- /dev/null +++ b/src/agent_api/dto/client.rs @@ -0,0 +1,6 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize)] +pub struct Client { + pub link: String, +} diff --git a/src/agent_api/dto/client_stats.rs b/src/agent_api/dto/client_stats.rs new file mode 100644 index 0000000..3624f18 --- /dev/null +++ b/src/agent_api/dto/client_stats.rs @@ -0,0 +1,4 @@ +pub struct ClientStats { + pub up: i64, + pub down: i64, +} diff --git a/src/agent_api/dto/mod.rs b/src/agent_api/dto/mod.rs new file mode 100644 index 0000000..50810e5 --- /dev/null +++ b/src/agent_api/dto/mod.rs @@ -0,0 +1,7 @@ +mod agent_response; +mod client; +mod client_stats; + +pub use agent_response::AgentResponse; +pub use client::Client; +pub use client_stats::ClientStats; diff --git a/src/agent_api/mod.rs b/src/agent_api/mod.rs new file mode 100644 index 0000000..3c0a8c7 --- /dev/null +++ b/src/agent_api/mod.rs @@ -0,0 +1,6 @@ +pub mod dto; +pub mod requests; +pub mod state; +pub mod utils; + +pub use state::AgentState; diff --git a/src/agent_api/requests/create_client.rs b/src/agent_api/requests/create_client.rs new file mode 100644 index 0000000..5f82318 --- /dev/null +++ b/src/agent_api/requests/create_client.rs @@ -0,0 +1,75 @@ +use serde_json::{json, Value}; +use uuid::Uuid; + +use crate::{ + agent_api::{ + dto::{AgentResponse, Client}, + utils, AgentState, + }, + data::enums::{Country, Protocol}, + enums::errors::internal::{AgentAPI, Error, Result}, +}; + +pub async fn create_client( + agent_state: &AgentState, + country: &Country, + protocol: &Protocol, + device_id: &Uuid, +) -> Result { + let agent_state = agent_state.clone(); + let cookie = agent_state.get_or_refresh_cookie(country).await?; + let agent = agent_state + .agents + .get(country) + .ok_or(Error::AgentAPI(AgentAPI::Internal))?; + let client = agent_state.client; + + let (inbound_id, client_body, link) = match protocol { + Protocol::Vless => { + let client_id = Uuid::new_v4(); + let client_body = utils::client_json::vless(&client_id, device_id); + let link = + utils::link::vless(&client_id, &agent.host, agent.vless_config.port, device_id); + let inbound_id = agent.vless_config.inbound_id; + (inbound_id, client_body, link) + } + Protocol::Shadowsocks => { + let password = utils::password_generator::shadowsocks(); + let client_body = utils::client_json::shadowsocks(&password, device_id); + let link = utils::link::shadowsocks( + &agent.password, + &password, + &agent.host, + agent.shadowsocks_config.port, + device_id, + ); + let inbound_id = agent.shadowsocks_config.inbound_id; + + (inbound_id, client_body, link) + } + }; + + let res = client + .post(format!( + "http://{}:{}/{}/panel/api/inbounds/addClient", + agent.host, agent.port, agent.secure_path + )) + .header("Cookie", cookie) + .json(&json!({ + "id": inbound_id, + "settings": { + "clients": [client_body] + } + } + )) + .send() + .await? + .json::>() + .await?; + + if !res.success { + return Err(Error::AgentAPI(AgentAPI::Internal)); + } + + Ok(Client { link }) +} diff --git a/src/agent_api/requests/delete_client.rs b/src/agent_api/requests/delete_client.rs new file mode 100644 index 0000000..eceeb72 --- /dev/null +++ b/src/agent_api/requests/delete_client.rs @@ -0,0 +1,44 @@ +use serde_json::Value; +use uuid::Uuid; + +use crate::{ + agent_api::{dto::AgentResponse, AgentState}, + data::enums::{Country, Protocol}, + enums::errors::internal::{AgentAPI, Error, Result}, +}; + +pub async fn delete_client( + agent_state: &AgentState, + country: &Country, + protocol: &Protocol, + device_id: &Uuid, +) -> Result<()> { + let agent_state = agent_state.clone(); + let cookie = agent_state.get_or_refresh_cookie(&country).await?; + let agent = agent_state + .agents + .get(&country) + .ok_or(Error::AgentAPI(AgentAPI::Internal))?; + let client = agent_state.client; + let inbound_id = match protocol { + Protocol::Vless => agent.vless_config.inbound_id, + Protocol::Shadowsocks => agent.shadowsocks_config.inbound_id, + }; + + let res = client + .post(format!( + "http://{}:{}/{}/panel/api/inbounds/{}/delClient/{}", + agent.host, agent.port, agent.secure_path, inbound_id, device_id + )) + .header("Cookie", cookie) + .send() + .await? + .json::>() + .await?; + + if !res.success { + return Err(Error::AgentAPI(AgentAPI::Internal)); + } + + Ok(()) +} diff --git a/src/agent_api/requests/get_client_stats.rs b/src/agent_api/requests/get_client_stats.rs new file mode 100644 index 0000000..49a9ce1 --- /dev/null +++ b/src/agent_api/requests/get_client_stats.rs @@ -0,0 +1,61 @@ +use serde::Deserialize; +use uuid::Uuid; + +use crate::{ + agent_api::{ + dto::{AgentResponse, ClientStats}, + AgentState, + }, + data::enums::Country, + enums::errors::internal::{AgentAPI, Error, Result}, +}; + +#[derive(Deserialize, Clone)] +#[allow(non_snake_case)] +struct ClientStatsResponse { + pub id: u32, + pub inboundId: u32, + pub enable: bool, + pub email: String, + pub up: i64, + pub down: i64, + pub expiryTime: u32, + pub total: u32, + pub reset: u32, +} + +pub async fn get_client_stats( + agent_state: &AgentState, + country: &Country, + device_id: &Uuid, +) -> Result { + let agent_state = agent_state.clone(); + let cookie = agent_state.get_or_refresh_cookie(&country).await?; + let agent = agent_state + .agents + .get(&country) + .ok_or(Error::AgentAPI(AgentAPI::Internal))?; + let client = agent_state.client; + + let res = client + .get(format!( + "http://{}:{}/{}/panel/api/inbounds/getClientTaffics/{}", + agent.host, agent.port, agent.secure_path, device_id + )) + .header("Cookie", cookie) + .send() + .await? + .json::>() + .await?; + + if !res.success { + return Err(Error::AgentAPI(AgentAPI::Internal)); + } + + let stats = ClientStats { + up: res.obj.clone().unwrap().up, + down: res.obj.unwrap().down, + }; + + Ok(stats) +} diff --git a/src/agent_api/requests/login.rs b/src/agent_api/requests/login.rs new file mode 100644 index 0000000..607245e --- /dev/null +++ b/src/agent_api/requests/login.rs @@ -0,0 +1,45 @@ +use reqwest::header::SET_COOKIE; +use serde_json::Value; +use tracing::error; + +use crate::{ + agent_api::{dto::AgentResponse, AgentState}, + data::enums::Country, + enums::errors::internal::{AgentAPI, Error, Result}, +}; + +pub async fn login(agent_state: &AgentState, country: &Country) -> Result { + let agent_state = agent_state.clone(); + let client = agent_state.client; + let agent = agent_state + .agents + .get(country) + .ok_or(Error::AgentAPI(AgentAPI::Internal))?; + let params = [("username", &agent.username), ("password", &agent.password)]; + + let res = client + .post(format!( + "http://{}:{}/{}/login", + agent.host, agent.port, agent.secure_path + )) + .form(¶ms) + .send() + .await + .map_err(|e| { + error!("Login failed: {}", e); + Error::AgentAPI(AgentAPI::Internal) + })?; + + let cookies = res.headers().get_all(SET_COOKIE).iter().last().cloned(); + let body = res.json::>().await?; + + if !body.success || cookies.is_none() { + error!("Login failed: {}", body.msg); + return Err(Error::AgentAPI(AgentAPI::Internal)); + } + + let cookies = cookies.unwrap().to_str().unwrap().to_string(); + println!("Cookies: {:?}", cookies.clone()); + + Ok(cookies) +} diff --git a/src/agent_api/requests/mod.rs b/src/agent_api/requests/mod.rs new file mode 100644 index 0000000..58b5d2d --- /dev/null +++ b/src/agent_api/requests/mod.rs @@ -0,0 +1,9 @@ +mod create_client; +mod delete_client; +mod get_client_stats; +mod login; + +pub use create_client::create_client; +pub use delete_client::delete_client; +pub use get_client_stats::get_client_stats; +pub use login::login; diff --git a/src/agent_api/state/agent.rs b/src/agent_api/state/agent.rs new file mode 100644 index 0000000..6016078 --- /dev/null +++ b/src/agent_api/state/agent.rs @@ -0,0 +1,54 @@ +use std::sync::Arc; + +use serde::{Deserialize, Serialize}; +use tokio::{sync::RwLock, time::Instant}; + +use super::{configs, cookie::Cookie}; + +#[derive(Clone)] +pub struct Agent { + pub host: String, + pub port: u16, + pub secure_path: String, + pub username: String, + pub password: String, + pub shadowsocks_config: configs::Shadowsocks, + pub vless_config: configs::Vless, + pub cookie: Arc>, +} + +#[derive(Clone, Serialize, Deserialize)] +struct AgentBuilder { + host: String, + port: u16, + secure_path: String, + username: String, + password: String, + shadowsocks_config: configs::Shadowsocks, + vless_config: configs::Vless, +} + +impl From for Agent { + fn from(builder: AgentBuilder) -> Self { + Agent { + host: builder.host, + port: builder.port, + secure_path: builder.secure_path, + username: builder.username, + password: builder.password, + shadowsocks_config: builder.shadowsocks_config, + vless_config: builder.vless_config, + cookie: Arc::new(RwLock::new(Cookie { + cookie: String::new(), + expires_at: Instant::now(), + })), + } + } +} + +impl From<&str> for Agent { + fn from(s: &str) -> Self { + let builder: AgentBuilder = serde_json::from_str(s).expect("Agent config is invalid"); + builder.into() + } +} diff --git a/src/agent_api/state/configs/mod.rs b/src/agent_api/state/configs/mod.rs new file mode 100644 index 0000000..c65c3ff --- /dev/null +++ b/src/agent_api/state/configs/mod.rs @@ -0,0 +1,5 @@ +mod shadowsocks; +mod vless; + +pub use shadowsocks::Shadowsocks; +pub use vless::Vless; diff --git a/src/agent_api/state/configs/shadowsocks.rs b/src/agent_api/state/configs/shadowsocks.rs new file mode 100644 index 0000000..a0e875e --- /dev/null +++ b/src/agent_api/state/configs/shadowsocks.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct Shadowsocks { + pub inbound_id: u32, + pub port: u16, + pub encryption: String, + pub password: String, +} diff --git a/src/agent_api/state/configs/vless.rs b/src/agent_api/state/configs/vless.rs new file mode 100644 index 0000000..573da83 --- /dev/null +++ b/src/agent_api/state/configs/vless.rs @@ -0,0 +1,7 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Serialize, Deserialize)] +pub struct Vless { + pub inbound_id: u32, + pub port: u16, +} diff --git a/src/agent_api/state/cookie.rs b/src/agent_api/state/cookie.rs new file mode 100644 index 0000000..41036db --- /dev/null +++ b/src/agent_api/state/cookie.rs @@ -0,0 +1,7 @@ +use tokio::time::Instant; + +#[derive(Clone)] +pub struct Cookie { + pub cookie: String, + pub expires_at: Instant, +} diff --git a/src/agent_api/state/mod.rs b/src/agent_api/state/mod.rs new file mode 100644 index 0000000..d5c4d38 --- /dev/null +++ b/src/agent_api/state/mod.rs @@ -0,0 +1,42 @@ +use std::collections::HashMap; +use tokio::time::{Duration, Instant}; + +pub use agent::Agent; + +use crate::{data::enums::Country, enums::errors::internal::Result}; + +use super::requests::login; + +mod agent; +mod configs; +mod cookie; + +#[derive(Clone)] +pub struct AgentState { + pub client: reqwest::Client, + pub agents: HashMap, +} + +impl AgentState { + pub fn new(servers: HashMap) -> Self { + AgentState { + agents: servers, + client: reqwest::Client::new(), + } + } + + pub async fn get_or_refresh_cookie(&self, country: &Country) -> Result { + let agent = self.agents.get(country).unwrap(); + let cookie = agent.cookie.read().await; + + if cookie.expires_at < Instant::now() { + let new_cookie = login(self, country).await?; + let mut cookie = agent.cookie.write().await; + println!("Refreshed cookie: {:?}", new_cookie); + cookie.cookie = new_cookie; + cookie.expires_at = Instant::now() + Duration::from_secs(60 * 59); + } + + Ok(cookie.cookie.clone()) + } +} diff --git a/src/agent_api/utils/client_json/mod.rs b/src/agent_api/utils/client_json/mod.rs new file mode 100644 index 0000000..c53499c --- /dev/null +++ b/src/agent_api/utils/client_json/mod.rs @@ -0,0 +1,5 @@ +mod shadowsocks; +mod vless; + +pub use shadowsocks::shadowsocks; +pub use vless::vless; diff --git a/src/agent_api/utils/client_json/shadowsocks.rs b/src/agent_api/utils/client_json/shadowsocks.rs new file mode 100644 index 0000000..c243928 --- /dev/null +++ b/src/agent_api/utils/client_json/shadowsocks.rs @@ -0,0 +1,13 @@ +use serde_json::json; +use uuid::Uuid; + +pub fn shadowsocks(password: &str, device_id: &Uuid) -> serde_json::Value { + json!({ + "email": device_id, + "password": password, + "limitIp": 0, + "totalGB": 0, + "expiryTime": 0, + "enable": true + }) +} diff --git a/src/agent_api/utils/client_json/vless.rs b/src/agent_api/utils/client_json/vless.rs new file mode 100644 index 0000000..b8dd057 --- /dev/null +++ b/src/agent_api/utils/client_json/vless.rs @@ -0,0 +1,14 @@ +use serde_json::json; +use uuid::Uuid; + +pub fn vless(client_id: &Uuid, device_id: &Uuid) -> serde_json::Value { + json!({ + "id": client_id, + "flow": "", + "email": device_id, + "limitIp": 0, + "totalGB": 0, + "expiryTime": 0, + "enable": true + }) +} diff --git a/src/agent_api/utils/link/mod.rs b/src/agent_api/utils/link/mod.rs new file mode 100644 index 0000000..c53499c --- /dev/null +++ b/src/agent_api/utils/link/mod.rs @@ -0,0 +1,5 @@ +mod shadowsocks; +mod vless; + +pub use shadowsocks::shadowsocks; +pub use vless::vless; diff --git a/src/agent_api/utils/link/shadowsocks.rs b/src/agent_api/utils/link/shadowsocks.rs new file mode 100644 index 0000000..d4ca3e9 --- /dev/null +++ b/src/agent_api/utils/link/shadowsocks.rs @@ -0,0 +1,18 @@ +use uuid::Uuid; + +pub fn shadowsocks( + inbound_password: &str, + client_password: &str, + host: &str, + port: u16, + device_id: &Uuid, +) -> String { + let hash = openssl::base64::encode_block( + format!( + "2022-blake3-aes-256-gcm:{}:{}", + inbound_password, client_password + ) + .as_bytes(), + ); + format!("ss://{}@{}:{}?type=tcp#{}", hash, host, port, device_id) +} diff --git a/src/agent_api/utils/link/vless.rs b/src/agent_api/utils/link/vless.rs new file mode 100644 index 0000000..7675a1a --- /dev/null +++ b/src/agent_api/utils/link/vless.rs @@ -0,0 +1,8 @@ +use uuid::Uuid; + +pub fn vless(client_id: &Uuid, host: &str, port: u16, device_id: &Uuid) -> String { + format!( + "vless://{}@{}:{}?type=tcp&security=none#{}", + client_id, host, port, device_id + ) +} diff --git a/src/agent_api/utils/mod.rs b/src/agent_api/utils/mod.rs new file mode 100644 index 0000000..f00eeb5 --- /dev/null +++ b/src/agent_api/utils/mod.rs @@ -0,0 +1,3 @@ +pub mod client_json; +pub mod link; +pub mod password_generator; diff --git a/src/agent_api/utils/password_generator/mod.rs b/src/agent_api/utils/password_generator/mod.rs new file mode 100644 index 0000000..c742812 --- /dev/null +++ b/src/agent_api/utils/password_generator/mod.rs @@ -0,0 +1,3 @@ +mod shadowsocks; + +pub use shadowsocks::shadowsocks; diff --git a/src/agent_api/utils/password_generator/shadowsocks.rs b/src/agent_api/utils/password_generator/shadowsocks.rs new file mode 100644 index 0000000..f2ff1a8 --- /dev/null +++ b/src/agent_api/utils/password_generator/shadowsocks.rs @@ -0,0 +1,7 @@ +use rand::Rng; + +pub fn shadowsocks() -> String { + let mut rng = rand::thread_rng(); + let random_numbers: Vec = (0..32).map(|_| rng.gen_range(0..=255)).collect(); + openssl::base64::encode_block(&random_numbers) +} diff --git a/src/config.rs b/src/config.rs index 55a9362..aa9eade 100644 --- a/src/config.rs +++ b/src/config.rs @@ -15,7 +15,7 @@ impl std::str::FromStr for ServerMode { match s { "development" => Ok(ServerMode::Development), "production" => Ok(ServerMode::Production), - _ => Err("Unknown server type".to_string()), + _ => Err("Unknown server mode".to_string()), } } } @@ -35,6 +35,7 @@ pub struct Config { pub google_client_id: String, pub resend_api_key: String, pub server_mode: ServerMode, + pub agent_config_uk: String, } pub static ENV: Lazy = Lazy::new(|| { @@ -60,6 +61,7 @@ pub static ENV: Lazy = Lazy::new(|| { .expect("SERVER_MODE must be set") .parse() .unwrap(), + agent_config_uk: env::var("AGENT_CONFIG_UK").expect("AGENT_CONFIG_UK must be set"), } }); diff --git a/src/data/enums/country.rs b/src/data/enums/country.rs index 0e96127..7343534 100644 --- a/src/data/enums/country.rs +++ b/src/data/enums/country.rs @@ -8,7 +8,9 @@ use diesel::{ }; use serde::{Deserialize, Serialize}; -#[derive(Debug, AsExpression, FromSqlRow, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +#[derive( + Debug, AsExpression, FromSqlRow, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Hash, +)] #[diesel(sql_type = crate::data::schema::sql_types::Country)] #[allow(clippy::upper_case_acronyms)] pub enum Country { diff --git a/src/data/enums/mod.rs b/src/data/enums/mod.rs index 4ab8f72..bfa69aa 100644 --- a/src/data/enums/mod.rs +++ b/src/data/enums/mod.rs @@ -1,20 +1,15 @@ -pub mod config_status; -pub use config_status::ConfigStatus; - pub mod country; -pub use country::Country; - pub mod device_status; -pub use device_status::DeviceStatus; - +pub mod oauth_provider; pub mod os; -pub use os::OS; - +pub mod protocol; pub mod session_status; -pub use session_status::SessionStatus; - pub mod user_status; -pub use user_status::UserStatus; -pub mod oauth_provider; +pub use country::Country; +pub use device_status::DeviceStatus; pub use oauth_provider::OAuthProvider; +pub use os::OS; +pub use protocol::Protocol; +pub use session_status::SessionStatus; +pub use user_status::UserStatus; diff --git a/src/data/enums/protocol.rs b/src/data/enums/protocol.rs new file mode 100644 index 0000000..1910b01 --- /dev/null +++ b/src/data/enums/protocol.rs @@ -0,0 +1,45 @@ +use std::io::Write; + +use diesel::{ + deserialize::{self, FromSql, FromSqlRow}, + expression::AsExpression, + pg::{Pg, PgValue}, + serialize::{self, IsNull, Output, ToSql}, +}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, AsExpression, FromSqlRow, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)] +#[diesel(sql_type = crate::data::schema::sql_types::Protocol)] +pub enum Protocol { + Vless, + Shadowsocks, +} + +impl ToSql for Protocol { + fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Pg>) -> serialize::Result { + match *self { + Protocol::Vless => out.write_all(b"Vless")?, + Protocol::Shadowsocks => out.write_all(b"Shadowsocks")?, + } + Ok(IsNull::No) + } +} + +impl FromSql for Protocol { + fn from_sql(bytes: PgValue) -> deserialize::Result { + match bytes.as_bytes() { + b"Vless" => Ok(Protocol::Vless), + b"Shadowsocks" => Ok(Protocol::Shadowsocks), + _ => Err("Unrecognized enum variant".into()), + } + } +} + +impl std::fmt::Display for Protocol { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Protocol::Vless => write!(f, "Vless"), + Protocol::Shadowsocks => write!(f, "Shadowsocks"), + } + } +} diff --git a/src/data/models/config.rs b/src/data/models/config.rs deleted file mode 100644 index 3dc8cc9..0000000 --- a/src/data/models/config.rs +++ /dev/null @@ -1,20 +0,0 @@ -use chrono::NaiveDateTime; -use diesel::{Queryable, Selectable}; -use serde::Serialize; -use uuid::Uuid; - -use crate::data::{enums::ConfigStatus, schema}; - -#[derive(Queryable, Selectable, Debug, Clone, Serialize)] -#[diesel(table_name = schema::config)] -#[diesel(belongs_to(crate::dto::server::Server,))] -#[diesel(check_for_backend(diesel::pg::Pg))] -pub struct Config { - pub id: Uuid, - pub private_key: String, - pub user_ip: String, - pub server_id: Uuid, - pub status: ConfigStatus, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} diff --git a/src/data/models/device.rs b/src/data/models/device.rs index cb9590d..71f3f5c 100644 --- a/src/data/models/device.rs +++ b/src/data/models/device.rs @@ -10,15 +10,12 @@ use crate::data::{ #[derive(Queryable, Selectable, Debug, Clone, Serialize)] #[diesel(table_name = schema::device)] -#[diesel(belongs_to(crate::dto::user::User))] #[diesel(check_for_backend(diesel::pg::Pg))] pub struct Device { pub id: Uuid, pub name: String, pub os: OS, pub user_id: Uuid, - pub banned_at: Option, - pub banned_till: Option, pub status: DeviceStatus, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, diff --git a/src/data/models/mod.rs b/src/data/models/mod.rs index 345dfb9..514fb1a 100644 --- a/src/data/models/mod.rs +++ b/src/data/models/mod.rs @@ -1,17 +1,13 @@ mod classic_auth; -mod config; mod device; mod email_confirmation; mod oauth; -mod server; mod session; mod user; pub use classic_auth::ClassicAuth; -pub use config::Config; pub use device::Device; pub use email_confirmation::EmailConfirmation; pub use oauth::OAuth; -pub use server::Server; pub use session::Session; pub use user::User; diff --git a/src/data/models/server.rs b/src/data/models/server.rs deleted file mode 100644 index d5ce0ac..0000000 --- a/src/data/models/server.rs +++ /dev/null @@ -1,18 +0,0 @@ -use chrono::NaiveDateTime; -use diesel::{Queryable, Selectable}; -use uuid::Uuid; - -use crate::data::{enums::Country, schema}; - -#[derive(Queryable, Selectable, Debug, Clone)] -#[diesel(table_name = schema::server)] -#[diesel(check_for_backend(diesel::pg::Pg))] -#[allow(dead_code)] -pub struct Server { - pub id: Uuid, - pub public_key: String, - pub wireguard_uri: String, - pub country: Country, - pub created_at: NaiveDateTime, - pub updated_at: NaiveDateTime, -} diff --git a/src/data/models/session.rs b/src/data/models/session.rs index f4d1136..4d31e13 100644 --- a/src/data/models/session.rs +++ b/src/data/models/session.rs @@ -2,19 +2,23 @@ use chrono::NaiveDateTime; use diesel::{Queryable, Selectable}; use uuid::Uuid; -use crate::data::{enums::SessionStatus, schema}; +use crate::data::{ + enums::{Country, Protocol, SessionStatus}, + schema, +}; #[derive(Queryable, Selectable, Debug, Clone)] #[diesel(table_name = schema::session)] -#[diesel(belongs_to(crate::dto::device::Device))] -#[diesel(belongs_to(crate::dto::config::Config))] #[diesel(check_for_backend(diesel::pg::Pg))] -#[allow(dead_code)] pub struct Session { pub id: Uuid, + pub device_id: Uuid, + pub protocol: Protocol, + pub country: Country, + pub link: String, pub status: SessionStatus, + pub up: Option, + pub down: Option, pub opened_at: NaiveDateTime, pub closed_at: Option, - pub device_id: Uuid, - pub config_id: Uuid, } diff --git a/src/data/models/user.rs b/src/data/models/user.rs index 56203c2..99db605 100644 --- a/src/data/models/user.rs +++ b/src/data/models/user.rs @@ -11,8 +11,6 @@ use crate::data::{enums::UserStatus, schema}; pub struct User { pub id: Uuid, pub email: String, - pub banned_at: Option, - pub banned_till: Option, pub status: UserStatus, pub created_at: NaiveDateTime, pub updated_at: NaiveDateTime, diff --git a/src/data/schema.rs b/src/data/schema.rs index b1c734f..c2e25c1 100644 --- a/src/data/schema.rs +++ b/src/data/schema.rs @@ -1,10 +1,6 @@ // @generated automatically by Diesel CLI. pub mod sql_types { - #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] - #[diesel(postgres_type(name = "ConfigStatus"))] - pub struct ConfigStatus; - #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "Country"))] pub struct Country; @@ -17,6 +13,14 @@ pub mod sql_types { #[diesel(postgres_type(name = "OS"))] pub struct Os; + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "OAuthProvider"))] + pub struct OAuthProvider; + + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] + #[diesel(postgres_type(name = "Protocol"))] + pub struct Protocol; + #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "SessionStatus"))] pub struct SessionStatus; @@ -24,39 +28,17 @@ pub mod sql_types { #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] #[diesel(postgres_type(name = "UserStatus"))] pub struct UserStatus; - - #[derive(diesel::query_builder::QueryId, diesel::sql_types::SqlType)] - #[diesel(postgres_type(name = "OAuthProvider"))] - pub struct OAuthProvider; -} - -diesel::table! { - use diesel::sql_types::*; - use super::sql_types::ConfigStatus; - - config (id) { - id -> Uuid, - private_key -> Text, - user_ip -> Text, - server_id -> Uuid, - status -> ConfigStatus, - created_at -> Timestamp, - updated_at -> Timestamp, - } } diesel::table! { use diesel::sql_types::*; - use super::sql_types::Os; - use super::sql_types::DeviceStatus; + use super::sql_types::{Os, DeviceStatus}; device (id) { id -> Uuid, name -> Text, os -> Os, user_id -> Uuid, - banned_at -> Nullable, - banned_till -> Nullable, status -> DeviceStatus, created_at -> Timestamp, updated_at -> Timestamp, @@ -65,29 +47,19 @@ diesel::table! { diesel::table! { use diesel::sql_types::*; - use super::sql_types::Country; - - server (id) { - id -> Uuid, - public_key -> Text, - wireguard_uri -> Text, - country -> Country, - created_at -> Timestamp, - updated_at -> Timestamp, - } -} - -diesel::table! { - use diesel::sql_types::*; - use super::sql_types::SessionStatus; + use super::sql_types::{SessionStatus, Country, Protocol}; session (id) { id -> Uuid, + device_id -> Uuid, + protocol -> Protocol, + country -> Country, + link -> Text, status -> SessionStatus, + up -> Nullable, + down -> Nullable, opened_at -> Timestamp, closed_at -> Nullable, - device_id -> Uuid, - config_id -> Uuid, } } @@ -98,8 +70,6 @@ diesel::table! { user (id) { id -> Uuid, email -> Text, - banned_at -> Nullable, - banned_till -> Nullable, status -> UserStatus, created_at -> Timestamp, updated_at -> Timestamp, @@ -147,15 +117,11 @@ diesel::table! { diesel::joinable!(classic_auth -> user (user_id)); diesel::joinable!(oauth -> user (user_id)); diesel::joinable!(email_confirmation -> user (user_id)); -diesel::joinable!(config -> server (server_id)); diesel::joinable!(device -> user (user_id)); -diesel::joinable!(session -> config (config_id)); diesel::joinable!(session -> device (device_id)); diesel::allow_tables_to_appear_in_same_query!( - config, device, - server, session, user, classic_auth, diff --git a/src/dto/session/interface.rs b/src/dto/session/interface.rs index cc56e59..ed93472 100644 --- a/src/dto/session/interface.rs +++ b/src/dto/session/interface.rs @@ -1,5 +1,5 @@ use crate::{ - data::models::{Config, Device, Server, Session}, + data::models::{Device, Session}, enums::errors::internal::Error, }; @@ -7,12 +7,12 @@ pub trait SessionBy { async fn get_session<'a>( &self, pool: &'a deadpool_diesel::postgres::Pool, - ) -> Result<(Session, Device, Config, Server), Error>; + ) -> Result<(Session, Device), Error>; } pub async fn get_session( pool: &deadpool_diesel::postgres::Pool, by: T, -) -> Result<(Session, Device, Config, Server), Error> { +) -> Result<(Session, Device), Error> { by.get_session(pool).await } diff --git a/src/dto/session/internal/mod.rs b/src/dto/session/internal/mod.rs index 5c0ad29..3309bca 100644 --- a/src/dto/session/internal/mod.rs +++ b/src/dto/session/internal/mod.rs @@ -1,5 +1,5 @@ -pub mod new_session; -pub use new_session::NewSession; +mod new_session; +mod session_history; -pub mod session_history; +pub use new_session::NewSession; pub use session_history::SessionHistory; diff --git a/src/dto/session/internal/new_session.rs b/src/dto/session/internal/new_session.rs index 4a0b981..dfc352b 100644 --- a/src/dto/session/internal/new_session.rs +++ b/src/dto/session/internal/new_session.rs @@ -1,12 +1,17 @@ use diesel::prelude::*; use uuid::Uuid; -use crate::data::{enums::SessionStatus, schema}; +use crate::data::{ + enums::{Country, Protocol, SessionStatus}, + schema, +}; #[derive(Insertable, Clone)] #[diesel(table_name = schema::session)] pub struct NewSession { pub status: SessionStatus, pub device_id: Uuid, - pub config_id: Uuid, + pub country: Country, + pub protocol: Protocol, + pub link: String, } diff --git a/src/dto/session/query/active_session_and_device.rs b/src/dto/session/query/active_session_and_device.rs index 61bb692..a711d0b 100644 --- a/src/dto/session/query/active_session_and_device.rs +++ b/src/dto/session/query/active_session_and_device.rs @@ -5,7 +5,7 @@ use uuid::Uuid; use crate::{ data::{ enums::SessionStatus, - models::{Config, Device, Server, Session}, + models::{Device, Session}, schema, }, dto::session::SessionBy, @@ -20,24 +20,18 @@ impl SessionBy for ActiveSessionAndDevice { async fn get_session<'a>( &self, pool: &'a deadpool_diesel::postgres::Pool, - ) -> Result<(Session, Device, Config, Server), Error> { + ) -> Result<(Session, Device), Error> { let conn = pool.get().await?; let device_id = self.device_id; - let result: Vec<(Session, Device, Config, Server)> = conn + let result: Vec<(Session, Device)> = conn .interact(move |conn| { schema::session::table .inner_join(schema::device::table) - .inner_join(schema::config::table.inner_join(schema::server::table)) .filter(schema::session::device_id.eq(device_id)) .filter(schema::session::status.eq(SessionStatus::Active)) - .select(( - Session::as_select(), - Device::as_select(), - Config::as_select(), - Server::as_select(), - )) - .load::<(Session, Device, Config, Server)>(conn) + .select((Session::as_select(), Device::as_select())) + .load::<(Session, Device)>(conn) }) .await??; if result.len() != 1 { diff --git a/src/dto/session/query/active_session_and_device_and_country.rs b/src/dto/session/query/active_session_and_device_and_country_and_protocol.rs similarity index 58% rename from src/dto/session/query/active_session_and_device_and_country.rs rename to src/dto/session/query/active_session_and_device_and_country_and_protocol.rs index 69e1b94..a814c16 100644 --- a/src/dto/session/query/active_session_and_device_and_country.rs +++ b/src/dto/session/query/active_session_and_device_and_country_and_protocol.rs @@ -4,42 +4,38 @@ use uuid::Uuid; use crate::{ data::{ - enums::{Country, SessionStatus}, - models::{Config, Device, Server, Session}, + enums::{Country, Protocol, SessionStatus}, + models::{Device, Session}, schema, }, dto::session::SessionBy, enums::errors::internal::{self, Error}, }; -pub struct ActiveSessionAndDeviceAndCountry { +pub struct ActiveSessionAndDeviceAndCountryAndProtocol { pub device_id: Uuid, pub country: Country, + pub protocol: Protocol, } -impl SessionBy for ActiveSessionAndDeviceAndCountry { +impl SessionBy for ActiveSessionAndDeviceAndCountryAndProtocol { async fn get_session<'a>( &self, pool: &'a deadpool_diesel::postgres::Pool, - ) -> Result<(Session, Device, Config, Server), Error> { + ) -> Result<(Session, Device), Error> { let conn = pool.get().await?; - let (device_id, country) = (self.device_id, self.country); - let result: Vec<(Session, Device, Config, Server)> = conn + let (device_id, country, protocol) = (self.device_id, self.country, self.protocol); + let result: Vec<(Session, Device)> = conn .interact(move |conn| { schema::session::table .inner_join(schema::device::table) - .inner_join(schema::config::table.inner_join(schema::server::table)) .filter(schema::session::device_id.eq(device_id)) .filter(schema::session::status.eq(SessionStatus::Active)) - .filter(schema::server::country.eq(country)) - .select(( - Session::as_select(), - Device::as_select(), - Config::as_select(), - Server::as_select(), - )) - .load::<(Session, Device, Config, Server)>(conn) + .filter(schema::session::country.eq(country)) + .filter(schema::session::protocol.eq(protocol)) + .select((Session::as_select(), Device::as_select())) + .load::<(Session, Device)>(conn) }) .await??; diff --git a/src/dto/session/query/mod.rs b/src/dto/session/query/mod.rs index a4a349e..3874f50 100644 --- a/src/dto/session/query/mod.rs +++ b/src/dto/session/query/mod.rs @@ -1,5 +1,5 @@ -pub mod active_session_and_device; -pub use active_session_and_device::ActiveSessionAndDevice; +mod active_session_and_device; +mod active_session_and_device_and_country_and_protocol; -pub mod active_session_and_device_and_country; -pub use active_session_and_device_and_country::ActiveSessionAndDeviceAndCountry; +pub use active_session_and_device::ActiveSessionAndDevice; +pub use active_session_and_device_and_country_and_protocol::ActiveSessionAndDeviceAndCountryAndProtocol; diff --git a/src/dto/session/request/create_session.rs b/src/dto/session/request/create_session.rs index 07a60f5..59153f7 100644 --- a/src/dto/session/request/create_session.rs +++ b/src/dto/session/request/create_session.rs @@ -1,8 +1,9 @@ use serde::{Deserialize, Serialize}; -use crate::data::enums::Country; +use crate::data::enums::{Country, Protocol}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct CreateSession { pub country: Country, + pub protocol: Protocol, } diff --git a/src/dto/session/response/session.rs b/src/dto/session/response/session.rs index 31bed5f..e58af91 100644 --- a/src/dto/session/response/session.rs +++ b/src/dto/session/response/session.rs @@ -6,20 +6,14 @@ use crate::data::models; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Session { pub session_id: Uuid, - pub server_public_key: String, - pub wireguard_uri: String, - pub user_ip: String, - pub user_private_key: String, + pub link: String, } -impl Session { - pub fn new(session: models::Session, server: models::Server, config: models::Config) -> Self { - Self { +impl From for Session { + fn from(session: models::Session) -> Self { + Session { session_id: session.id, - server_public_key: server.public_key, - wireguard_uri: server.wireguard_uri, - user_ip: config.user_ip, - user_private_key: config.private_key, + link: session.link, } } } diff --git a/src/enums/errors/internal/agent_api.rs b/src/enums/errors/internal/agent_api.rs new file mode 100644 index 0000000..4f0e881 --- /dev/null +++ b/src/enums/errors/internal/agent_api.rs @@ -0,0 +1,14 @@ +#[derive(Debug, Clone)] +pub enum AgentAPI { + Internal, + LoginFailed, +} + +impl std::fmt::Display for AgentAPI { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AgentAPI::LoginFailed => write!(f, "Login failed"), + AgentAPI::Internal => write!(f, "Internal error"), + } + } +} diff --git a/src/enums/errors/internal/mod.rs b/src/enums/errors/internal/mod.rs index e5d71ad..8a81e51 100644 --- a/src/enums/errors/internal/mod.rs +++ b/src/enums/errors/internal/mod.rs @@ -1,3 +1,4 @@ +mod agent_api; mod auth; mod database; mod device; @@ -7,6 +8,7 @@ mod resend; mod session; mod token; +pub use agent_api::AgentAPI; pub use auth::Auth; pub use database::Database; pub use device::Device; @@ -26,6 +28,7 @@ pub enum Error { Reqwest(Reqwest), Resend(Resend), Device(Device), + AgentAPI(AgentAPI), } impl std::fmt::Display for Error { @@ -39,6 +42,7 @@ impl std::fmt::Display for Error { Error::Reqwest(e) => write!(f, "{}", e), Error::Resend(e) => write!(f, "{}", e), Error::Device(e) => write!(f, "{}", e), + Error::AgentAPI(e) => write!(f, "{}", e), } } } diff --git a/src/handlers/device/revoke_device.rs b/src/handlers/device/revoke_device.rs index b4fbb8a..5be72be 100644 --- a/src/handlers/device/revoke_device.rs +++ b/src/handlers/device/revoke_device.rs @@ -2,7 +2,6 @@ use axum::{ extract::{Path, State}, http::StatusCode, }; -use deadpool_diesel::postgres::Pool; use tracing::info; use uuid::Uuid; @@ -10,14 +9,15 @@ use crate::{ dto::{auth::internal::AccessToken, response::success::Response}, enums::errors::external::Result, services::device, + state::AppState, }; pub async fn revoke_device( claims: AccessToken, - State(pool): State, + State(state): State, Path(device_id): Path, ) -> Result> { - device::revoke(&pool, claims, &device_id).await?; + device::revoke(&state.pool, &state.agent_state, claims, &device_id).await?; info!("Device revoked successfully: {:?}", device_id); diff --git a/src/handlers/session/close_session.rs b/src/handlers/session/close_session.rs index 3ed8577..d2cf4f2 100644 --- a/src/handlers/session/close_session.rs +++ b/src/handlers/session/close_session.rs @@ -1,18 +1,18 @@ use axum::{extract::State, http::StatusCode}; -use deadpool_diesel::postgres::Pool; use tracing::info; use crate::{ dto::{auth::internal::AccessToken, response::success::Response}, enums::errors::external::Result, services::session, + state::AppState, }; pub async fn close_session( claims: AccessToken, - State(pool): State, + State(state): State, ) -> Result> { - let session_id = session::close(&pool, &claims.device_id).await?; + let session_id = session::close(&state.pool, &state.agent_state, &claims.device_id).await?; info!("Closed session successfully: {}", session_id); diff --git a/src/handlers/session/create_session.rs b/src/handlers/session/create_session.rs index 2ba4f29..4c68e43 100644 --- a/src/handlers/session/create_session.rs +++ b/src/handlers/session/create_session.rs @@ -1,5 +1,4 @@ use axum::{extract::State, http::StatusCode}; -use deadpool_diesel::postgres::Pool; use tracing::info; use crate::{ @@ -11,14 +10,22 @@ use crate::{ enums::errors::external::Result, extractors::Json, services::session, + state::AppState, }; pub async fn create_session( claims: AccessToken, - State(pool): State, + State(state): State, Json(payload): Json, ) -> Result> { - let session = session::create(&pool, &claims.device_id, &payload.country).await?; + let session = session::create( + &state.pool, + &state.agent_state, + &payload.country, + &payload.protocol, + &claims.device_id, + ) + .await?; info!("Session created successfully: {}", session.session_id); diff --git a/src/main.rs b/src/main.rs index 110eae1..6ff0e2b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ extern crate openssl; #[macro_use] extern crate diesel; +mod agent_api; mod config; mod data; mod dto; diff --git a/src/services/config/get_by_country.rs b/src/services/config/get_by_country.rs deleted file mode 100644 index 64ee0ac..0000000 --- a/src/services/config/get_by_country.rs +++ /dev/null @@ -1,36 +0,0 @@ -use diesel::{prelude::*, QueryDsl}; -use tracing::info; - -use crate::{ - data::{ - enums::{ConfigStatus, Country}, - models::{Config, Server}, - schema, - }, - enums::errors::internal::Result, -}; - -pub async fn get_by_country( - pool: &deadpool_diesel::postgres::Pool, - country: &Country, -) -> Result<(Config, Server)> { - let conn = pool.get().await?; - let country = country.to_owned(); - - let result: Vec<(Config, Server)> = conn - .interact(move |conn| { - schema::config::table - .inner_join(schema::server::table) - .filter(schema::server::country.eq(country)) - .filter(schema::config::status.eq(ConfigStatus::NotInUse)) - .select((Config::as_select(), Server::as_select())) - .load::<(Config, Server)>(conn) - }) - .await??; - - let (config, server) = result.first().unwrap().clone(); - - info!("Got config by country: {:?}", country); - - Ok((config, server)) -} diff --git a/src/services/config/mod.rs b/src/services/config/mod.rs deleted file mode 100644 index 60632df..0000000 --- a/src/services/config/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod get_by_country; - -pub use get_by_country::get_by_country; diff --git a/src/services/device/revoke.rs b/src/services/device/revoke.rs index 2dd47aa..afb3428 100644 --- a/src/services/device/revoke.rs +++ b/src/services/device/revoke.rs @@ -3,6 +3,7 @@ use tracing::info; use uuid::Uuid; use crate::{ + agent_api, data::{enums::DeviceStatus, schema}, dto::{ auth::internal::AccessToken, @@ -14,6 +15,7 @@ use crate::{ pub async fn revoke( pool: &deadpool_diesel::postgres::Pool, + agent_state: &agent_api::AgentState, author: AccessToken, device_id: &Uuid, ) -> Result<()> { @@ -36,8 +38,8 @@ pub async fn revoke( return Err(Error::Device(Device::AlreadyRevoked)); } - if let Ok((session, _, _, _)) = get_session(pool, ActiveSessionAndDevice { device_id }).await { - let _ = services::session::close_by_id(pool, &session.id).await?; + if let Ok((session, _)) = get_session(pool, ActiveSessionAndDevice { device_id }).await { + let _ = services::session::close_by_id(pool, agent_state, &session.id).await?; } conn.interact(move |conn| { diff --git a/src/services/mod.rs b/src/services/mod.rs index d8a8df7..aa8f2fe 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -1,4 +1,3 @@ -pub mod config; pub mod device; pub mod email; pub mod oauth_providers; diff --git a/src/services/session/close.rs b/src/services/session/close.rs index fc1a70a..0865db3 100644 --- a/src/services/session/close.rs +++ b/src/services/session/close.rs @@ -1,19 +1,25 @@ use uuid::Uuid; -use super::close_by_id; use crate::{ + agent_api, dto::session::{interface::get_session, query::ActiveSessionAndDevice}, enums::errors::internal::Result, }; -pub async fn close(pool: &deadpool_diesel::postgres::Pool, device_id: &Uuid) -> Result { - let (session, _, _, _) = get_session( +use super::close_by_id; + +pub async fn close( + pool: &deadpool_diesel::postgres::Pool, + agent_state: &agent_api::AgentState, + device_id: &Uuid, +) -> Result { + let (session, _) = get_session( pool, ActiveSessionAndDevice { device_id: *device_id, }, ) .await?; - let session_id = close_by_id(pool, &session.id).await?; + let session_id = close_by_id(pool, agent_state, &session.id).await?; Ok(session_id) } diff --git a/src/services/session/close_by_id.rs b/src/services/session/close_by_id.rs index fb6e147..68f6f34 100644 --- a/src/services/session/close_by_id.rs +++ b/src/services/session/close_by_id.rs @@ -3,44 +3,35 @@ use tracing::info; use uuid::Uuid; use crate::{ - data::{ - enums::{ConfigStatus, SessionStatus}, - models::Session, - schema, - }, + agent_api, + data::{models::Session, schema}, enums::errors::internal::{self, Error, Result}, }; pub async fn close_by_id( pool: &deadpool_diesel::postgres::Pool, + agent_state: &agent_api::AgentState, session_id: &Uuid, ) -> Result { let conn = pool.get().await?; let session_id = *session_id; - conn.interact(move |conn| { - let session = match diesel::update(schema::session::table) - .filter(schema::session::id.eq(session_id)) - .set(( - schema::session::status.eq(SessionStatus::Closed), - schema::session::closed_at.eq(diesel::dsl::now), - )) - .get_result::(conn) - .map_err(|_| Error::Session(internal::Session::NotFound)) - { - Ok(session) => { - info!("Found session: {}", session.id); - session - } - Err(_) => return Err(Error::Session(internal::Session::NotFound)), - }; - let _ = diesel::update(schema::config::table) - .filter(schema::config::id.eq(session.config_id)) - .set(schema::config::status.eq(ConfigStatus::NotInUse)) - .execute(conn); - Ok(()) - }) - .await??; + let session = conn + .interact(move |conn| { + schema::session::table + .filter(schema::session::id.eq(session_id)) + .first::(conn) + }) + .await? + .map_err(|_| Error::Session(internal::Session::NotFound))?; + + agent_api::requests::delete_client( + agent_state, + &session.country, + &session.protocol, + &session.device_id, + ) + .await?; info!("Closed session: {}", session_id); diff --git a/src/services/session/create.rs b/src/services/session/create.rs index 0235a41..74900e4 100644 --- a/src/services/session/create.rs +++ b/src/services/session/create.rs @@ -4,67 +4,72 @@ use uuid::Uuid; use super::close_by_id; use crate::{ + agent_api, data::{ - enums::{ConfigStatus, Country, SessionStatus}, + enums::{Country, Protocol, SessionStatus}, models::Session, schema, }, dto::session::{ interface::get_session, internal::NewSession, - query::{ActiveSessionAndDevice, ActiveSessionAndDeviceAndCountry}, + query::{ActiveSessionAndDevice, ActiveSessionAndDeviceAndCountryAndProtocol}, response, }, enums::errors::internal::Result, - services::config::get_by_country, }; pub async fn create( pool: &deadpool_diesel::postgres::Pool, - device_id: &Uuid, + agent_state: &agent_api::AgentState, country: &Country, + protocol: &Protocol, + device_id: &Uuid, ) -> Result { let conn = pool.get().await?; - let (device_id, country) = (*device_id, *country); + let (device_id, country, protocol) = (*device_id, *country, *protocol); if let Ok(current_session) = get_session( pool, - ActiveSessionAndDeviceAndCountry { device_id, country }, + ActiveSessionAndDeviceAndCountryAndProtocol { + device_id, + country, + protocol, + }, ) .await { - let (session, _device, config, server) = current_session; - let response = response::Session::new(session.clone(), server, config); - info!("Found active session with the same country: {}", session.id); + let (session, _device) = current_session; + let response = response::Session::from(session.clone()); + info!( + "Found active session with the same country and protocol: {}", + session.id + ); return Ok(response); } - if let Ok((session, _, _, _)) = get_session(pool, ActiveSessionAndDevice { device_id }).await { - let _ = close_by_id(pool, &session.id).await?; + if let Ok((session, _)) = get_session(pool, ActiveSessionAndDevice { device_id }).await { + let _ = close_by_id(pool, agent_state, &session.id).await?; } - let (config, server) = get_by_country(pool, &country).await?; - let new_session = NewSession { + let new_client = + agent_api::requests::create_client(agent_state, &country, &protocol, &device_id).await?; + let data = NewSession { status: SessionStatus::Active, device_id, - config_id: config.id, + country, + protocol, + link: new_client.link, }; - - let session: Session = conn + let new_session: Session = conn .interact(move |conn| { - let session = diesel::insert_into(schema::session::table) - .values(&new_session) - .get_result::(conn); - let _ = diesel::update(schema::config::table) - .filter(schema::config::id.eq(config.id)) - .set(schema::config::status.eq(ConfigStatus::InUse)) - .execute(conn); - - session + diesel::insert_into(schema::session::table) + .values(&data) + .get_result(conn) }) .await??; - info!("Created session: {}", session.id); + info!("Created session: {}", new_session.id); - Ok(response::Session::new(session, server, config)) + Ok(response::Session::from(new_session)) } diff --git a/src/services/session/get_history.rs b/src/services/session/get_history.rs index dca5448..9655447 100644 --- a/src/services/session/get_history.rs +++ b/src/services/session/get_history.rs @@ -4,7 +4,7 @@ use uuid::Uuid; use crate::{ data::{ enums::{Country, SessionStatus}, - models::{Config, Server, Session}, + models::Session, schema, }, dto::session::{internal::SessionHistory, request::Params}, @@ -16,57 +16,5 @@ pub async fn get_history( user_id: &Uuid, params: &Params, ) -> Result> { - let conn = pool.get().await?; - - let limit = params.limit.unwrap_or(10); - let offset = params.offset.unwrap_or(0); - let devices = params.devices.clone(); - let countries = params.countries.clone(); - let user_id = *user_id; - - let data: Vec<(Session, Config, Server)> = conn - .interact(move |conn| { - schema::session::table - .inner_join(schema::config::table.inner_join(schema::server::table)) - .filter(schema::session::status.eq(SessionStatus::Closed)) - .filter( - schema::server::country.eq_any( - countries.unwrap_or( - schema::server::table - .select(schema::server::country) - .load::(conn)?, - ), - ), - ) - .filter( - schema::session::device_id.eq_any( - devices.unwrap_or( - schema::device::table - .select(schema::device::id) - .filter(schema::device::user_id.eq(user_id)) - .load::(conn)?, - ), - ), - ) - .limit(limit) - .offset(offset) - .select(( - Session::as_select(), - Config::as_select(), - Server::as_select(), - )) - .load::<(Session, Config, Server)>(conn) - }) - .await??; - - let history = data.iter().map(|(session, _, server)| SessionHistory { - id: session.id, - device_id: session.device_id, - opened_at: session.opened_at, - closed_at: session.closed_at.unwrap(), - duration: (session.closed_at.unwrap() - session.opened_at).num_seconds(), - country: server.country, - }); - - Ok(history.collect()) + todo!() } diff --git a/src/state.rs b/src/state.rs index 971d945..94c09c3 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,9 +1,15 @@ +use std::collections::HashMap; + use axum::extract::FromRef; use deadpool_diesel::postgres::{Manager, Pool}; use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; use resend_rs::Resend; -use crate::{config::ENV, data::enums::OAuthProvider}; +use crate::{ + agent_api::{state, AgentState}, + config::ENV, + data::enums::{Country, OAuthProvider}, +}; #[derive(Clone)] pub struct OAuthProviders { @@ -69,6 +75,8 @@ pub struct AppState { pub pool: Pool, pub oauth_providers: OAuthProviders, pub resend: Resend, + pub reqwest_client: reqwest::Client, + pub agent_state: AgentState, } impl AppState { @@ -76,10 +84,21 @@ impl AppState { let manager = Manager::new(&ENV.database_url, deadpool_diesel::Runtime::Tokio1); let pool = Pool::builder(manager).build().unwrap(); let resend = Resend::new(&ENV.resend_api_key); + let reqwest_client = reqwest::Client::new(); + let mut servers: HashMap = HashMap::new(); + servers.insert( + Country::UK, + state::Agent::from(ENV.agent_config_uk.as_str()), + ); + + let agent_state = AgentState::new(servers); + Self { pool, resend, oauth_providers: OAuthProviders::default(), + reqwest_client, + agent_state, } } } @@ -101,3 +120,9 @@ impl FromRef for Resend { state.resend.clone() } } + +impl FromRef for reqwest::Client { + fn from_ref(state: &AppState) -> Self { + state.reqwest_client.clone() + } +} diff --git a/src/tests/e2e/sql/seed.sql b/src/tests/e2e/sql/seed.sql index f9d5df7..337dc1f 100644 --- a/src/tests/e2e/sql/seed.sql +++ b/src/tests/e2e/sql/seed.sql @@ -1,11 +1,7 @@ -INSERT INTO "server" ("public_key", "wireguard_uri", "country") +INSERT INTO + "user" (email) VALUES - ('public_key_1', 'wireguard_uri_1', 'UK'), - ('public_key_2', 'wireguard_uri_2', 'USA'), - ('public_key_3', 'wireguard_uri_3', 'Germany'); + ('test@email.com'); -INSERT INTO "config" ("private_key", "user_ip", "server_id") -VALUES - ('private_key_1', '192.168.1.1', (SELECT "id" FROM "server" WHERE "public_key" = 'public_key_1')), - ('private_key_2', '192.168.1.2', (SELECT "id" FROM "server" WHERE "public_key" = 'public_key_2')), - ('private_key_3', '192.168.1.3', (SELECT "id" FROM "server" WHERE "public_key" = 'public_key_3')); +INSERT INTO "device" (name, os, user_id) +VALUES ('Pixel 8a', 'Android', (SELECT id from "user" WHERE email = 'test@email.com'));