restart trifid-api to do it better
This commit is contained in:
parent
9a5ffcd622
commit
7d2e370060
File diff suppressed because it is too large
Load Diff
|
@ -1,2 +0,0 @@
|
|||
[build]
|
||||
rustc-wrapper = "sccache"
|
|
@ -1 +1 @@
|
|||
DATABASE_URL=postgres://postgres@localhost/trifidapi
|
||||
DATABASE_URL=postgres://postgres@localhost/hotel
|
File diff suppressed because it is too large
Load Diff
|
@ -6,23 +6,17 @@ edition = "2021"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rocket = { version = "0.5.0-rc.2", features = ["json"] }
|
||||
base64 = "0.21.0"
|
||||
log = "0.4.17"
|
||||
sqlx = { version = "0.6", features = [ "runtime-tokio-native-tls" , "postgres" ] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
toml = "0.7.1"
|
||||
serde = "1.0.152"
|
||||
dotenvy = "0.15.6"
|
||||
paste = "1.0.11"
|
||||
totp-rs = { version = "4.2.0", features = ["qr", "otpauth", "gen_secret"]}
|
||||
uuid = { version = "1.3.0", features = ["v4", "fast-rng", "macro-diagnostics"]}
|
||||
url = { version = "2.3.1", features = ["serde"] }
|
||||
urlencoding = "2.1.2"
|
||||
chrono = "0.4.23"
|
||||
aes-gcm = "0.10.1"
|
||||
hex = "0.4.3"
|
||||
rand = "0.8.5"
|
||||
trifid-pki = { version = "0.1.3", path = "../trifid-pki" }
|
||||
sha2 = "0.10.6"
|
||||
ipnet = { version = "2.7.1", features = ["serde"] }
|
||||
actix-web = "4" # Web framework
|
||||
|
||||
serde = { version = "1", features = ["derive"] } # Serialization and deserialization
|
||||
|
||||
once_cell = "1" # Config
|
||||
toml = "0.7" # Config
|
||||
|
||||
log = "0.4" # Logging
|
||||
simple_logger = "4" # Logging
|
||||
|
||||
sea-orm = { version = "^0", features = [ "sqlx-postgres", "runtime-actix-rustls", "macros" ]} # Database
|
||||
|
||||
rand = "0.8" # Misc.
|
||||
hex = "0.4" # Misc.
|
|
@ -1,20 +1,3 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
// generated by `sqlx migrate build-script`
|
||||
fn main() {
|
||||
// trigger recompilation when a new migration is added
|
||||
println!("cargo:rerun-if-changed=migrations");
|
||||
println!("cargo:rerun-if-changed=migrations/");
|
||||
}
|
|
@ -1,81 +0,0 @@
|
|||
##################################
|
||||
# trifid-api example config file #
|
||||
##################################
|
||||
# trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
# Copyright (C) 2023 c0repwn3r
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <https:#www.gnu.org/licenses/>.
|
||||
|
||||
# Please read this file in it's entirety to learn what options you do or don't need to change
|
||||
# to get a functional trifid-api instance.
|
||||
|
||||
# What port should the API server listen on?
|
||||
# e.g. 8000 would mean the server is reachable at localhost:8000.
|
||||
# You probably don't need to change this.
|
||||
listen_port = 8000
|
||||
|
||||
# What is the postgres connection url to connect to the database?
|
||||
# Example: postgres://username:password@database_host/database_name
|
||||
# You absolutely need to change this.
|
||||
db_url = "postgres://postgres@localhost/trifidapi"
|
||||
|
||||
# What is the externally accessible URL of this instance?
|
||||
# If you are running behind a reverse proxy, or a domain name, or similar,
|
||||
# you need to set this to the URL that the web UI can make requests to.
|
||||
# e.g. http://localhost:8000
|
||||
# Reminder: this ip needs to be internet-accessible.
|
||||
# You absolutely need to change this.
|
||||
base = "http://localhost:8000"
|
||||
|
||||
# What is the externally accessible URL of the **web ui** for this instance?
|
||||
# This URL will be used to generate magic links, and needs to be correct.
|
||||
# You absolutely need to change this.
|
||||
web_root = "http://localhost:5173"
|
||||
|
||||
# How long should magic links be valid for (in seconds)?
|
||||
# You probably don't need to change this, 86400 (24 hours) is a sane default.
|
||||
magic_links_valid_for = 86400
|
||||
|
||||
# How long should session tokens be valid for (in seconds)?
|
||||
# This controls how long a user can go without requesting a new "magic link" to re-log-in.
|
||||
# This is a completley independent timer than `totp_verification_valid_for` - the auth token can (and often will) expire
|
||||
# while the session token remains completley valid.
|
||||
# You probably don't need to change this, 86400 (24 hours) is a sane default.
|
||||
session_tokens_valid_for = 86400
|
||||
|
||||
# How long should 2FA authentication be valid for (in seconds)?
|
||||
# This controls how long a user can remain logged in without having to re-do the 2FA authentication process.
|
||||
# This is a completley independent timer than `session_tokens_valid_for` - the session token can expire while the 2FA token
|
||||
# remains completley valid.
|
||||
# You probably don't need to change this, 3600 (1 hour) is a sane default.
|
||||
totp_verification_valid_for = 3600
|
||||
|
||||
# The per-instance data encryption key to protect sensitive data in the instance.
|
||||
# YOU ABSOLUTELY NEED TO CHANGE THIS. If you don't change anything else in this file, this should be the one thing you change.
|
||||
|
||||
# This should be a 32-byte hex value. Generate it with `openssl rand -hex 32`, or any other tool of your choice.
|
||||
# If you get "InvalidLength" errors while trying to do anything involving organizations, that indicates that this
|
||||
# value was improperly generated.
|
||||
#
|
||||
# ------- WARNING -------
|
||||
# Do not change this value in a production instance. It will make existing data inaccessible until changed back.
|
||||
# ------- WARNING -------
|
||||
data_key = "edd600bcebea461381ea23791b6967c8667e12827ac8b94dc022f189a5dc59a2"
|
||||
|
||||
# How long should CA certs be valid for before they need to be replaced (in seconds)?
|
||||
# This controls the maximum amount of time a network on this instance can go
|
||||
# without a rekey.
|
||||
# You probably don't need to change, this, 31536000 (1 year) is a sane default.
|
||||
# This value only affects new certs signed by this instance.
|
||||
ca_certs_valid_for = 31536000
|
|
@ -1,9 +0,0 @@
|
|||
listen_port = 8000
|
||||
db_url = "postgres://postgres@localhost/trifidapi"
|
||||
base = "http://localhost:8000"
|
||||
web_root = "http://localhost:5173"
|
||||
magic_links_valid_for = 86400
|
||||
session_tokens_valid_for = 86400
|
||||
totp_verification_valid_for = 3600
|
||||
data_key = "edd600bcebea461381ea23791b6967c8667e12827ac8b94dc022f189a5dc59a2"
|
||||
ca_certs_valid_for = 31536000
|
|
@ -0,0 +1,5 @@
|
|||
[print_schema]
|
||||
file = "src/schema.rs"
|
||||
|
||||
[migrations_directory]
|
||||
dir = "migrations"
|
|
@ -1,29 +0,0 @@
|
|||
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
-- Copyright (C) 2023 c0repwn3r
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||
|
||||
CREATE TABLE users (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
email VARCHAR(320) NOT NULL UNIQUE,
|
||||
created_on INTEGER NOT NULL, -- Unix (seconds) timestamp of user creation
|
||||
|
||||
banned INTEGER NOT NULL, -- Is the user banned? 1=Yes 0=No
|
||||
ban_reason VARCHAR(1024) NOT NULL, -- What is the reason for this user's ban?
|
||||
|
||||
totp_secret VARCHAR(128) NOT NULL,
|
||||
totp_verified INTEGER NOT NULL,
|
||||
totp_otpurl VARCHAR(3000) NOT NULL
|
||||
);
|
||||
CREATE INDEX idx_users_email ON users(email);
|
|
@ -1,21 +0,0 @@
|
|||
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
-- Copyright (C) 2023 c0repwn3r
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||
|
||||
CREATE TABLE magic_links (
|
||||
id VARCHAR(39) NOT NULL PRIMARY KEY UNIQUE,
|
||||
user_id SERIAL NOT NULL REFERENCES users(id),
|
||||
expires_on INTEGER NOT NULL -- Unix (seconds) timestamp of when this link expires
|
||||
);
|
|
@ -1,21 +0,0 @@
|
|||
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
-- Copyright (C) 2023 c0repwn3r
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||
|
||||
CREATE TABLE session_tokens (
|
||||
id VARCHAR(39) NOT NULL PRIMARY KEY,
|
||||
user_id SERIAL NOT NULL REFERENCES users(id),
|
||||
expires_on INTEGER NOT NULL -- Unix (seconds) timestamp of when this session expires
|
||||
);
|
|
@ -1,21 +0,0 @@
|
|||
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
-- Copyright (C) 2023 c0repwn3r
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||
|
||||
CREATE TABLE auth_tokens (
|
||||
id VARCHAR(39) NOT NULL PRIMARY KEY,
|
||||
user_id SERIAL NOT NULL REFERENCES users(id),
|
||||
session_token VARCHAR(39) NOT NULL REFERENCES session_tokens(id)
|
||||
);
|
|
@ -1,22 +0,0 @@
|
|||
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
-- Copyright (C) 2023 c0repwn3r
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||
|
||||
CREATE TABLE totp_create_tokens (
|
||||
id VARCHAR(41) NOT NULL PRIMARY KEY,
|
||||
expires_on INTEGER NOT NULL, -- The unix (seconds) timestamp of when this TOTP create token expires
|
||||
totp_otpurl VARCHAR(3000) NOT NULL,
|
||||
totp_secret VARCHAR(128) NOT NULL
|
||||
);
|
|
@ -1,24 +0,0 @@
|
|||
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
-- Copyright (C) 2023 c0repwn3r
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||
|
||||
CREATE TABLE organizations (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
owner SERIAL NOT NULL REFERENCES users(id),
|
||||
ca_key VARCHAR(3072) NOT NULL, -- The hex-encoded ENCRYPTED (see below) concatenation of all CA keys on this org
|
||||
ca_crt VARCHAR(3072) NOT NULL, -- The concatenation of all CA certificates on this org. This is passed directly to NebulaCAPool
|
||||
iv VARCHAR(128) NOT NULL -- The 12-byte hex-encoded IV, used to encrypt ca_key with the instance AES key
|
||||
);
|
||||
CREATE INDEX idx_organizations_owner ON organizations(owner);
|
|
@ -1,23 +0,0 @@
|
|||
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
-- Copyright (C) 2023 c0repwn3r
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||
|
||||
CREATE TABLE organization_authorized_users (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
user_id SERIAL NOT NULL REFERENCES users(id),
|
||||
org_id SERIAL NOT NULL REFERENCES organizations(id)
|
||||
);
|
||||
CREATE INDEX idx_organization_authorized_users_user ON organization_authorized_users(user_id);
|
||||
CREATE INDEX idx_organization_authorized_users_org ON organization_authorized_users(org_id);
|
|
@ -1,20 +0,0 @@
|
|||
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
-- Copyright (C) 2023 c0repwn3r
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||
|
||||
CREATE TABLE cacheddata (
|
||||
datakey VARCHAR(256) NOT NULL PRIMARY KEY,
|
||||
datavalue VARCHAR(2048) NOT NULL
|
||||
);
|
|
@ -1,22 +0,0 @@
|
|||
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
-- Copyright (C) 2023 c0repwn3r
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||
|
||||
CREATE TABLE roles (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
org SERIAL NOT NULL REFERENCES organizations(id),
|
||||
name VARCHAR(128) NOT NULL,
|
||||
description VARCHAR(4096) NOT NULL
|
||||
);
|
|
@ -1,27 +0,0 @@
|
|||
-- trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
-- Copyright (C) 2023 c0repwn3r
|
||||
--
|
||||
-- This program is free software: you can redistribute it and/or modify
|
||||
-- it under the terms of the GNU General Public License as published by
|
||||
-- the Free Software Foundation, either version 3 of the License, or
|
||||
-- (at your option) any later version.
|
||||
--
|
||||
-- This program is distributed in the hope that it will be useful,
|
||||
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
-- GNU General Public License for more details.
|
||||
--
|
||||
-- You should have received a copy of the GNU General Public License
|
||||
-- along with this program. If not, see <https:--www.gnu.org/licenses/>.
|
||||
|
||||
CREATE TABLE roles_firewall_rules (
|
||||
id SERIAL NOT NULL PRIMARY KEY,
|
||||
role SERIAL NOT NULL REFERENCES roles(id),
|
||||
protocol INTEGER NOT NULL, -- 0: any 1: tcp 2: udp 3: icmp
|
||||
port_range_start INTEGER NOT NULL, -- min: 1 max: 65535. Ignored if protocol==3
|
||||
port_range_end INTEGER NOT NULL, -- min: 1 max: 65535, must be greater than or equal to port_range_start. Ignored if protocol==3
|
||||
allow_from INTEGER NOT NULL, -- Allow traffic goverened by above rules from who?
|
||||
-- -1: anybody
|
||||
-- (a role, anything else): only that role
|
||||
description VARCHAR(4096) NOT NULL
|
||||
);
|
|
@ -1,144 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use rocket::http::Status;
|
||||
use rocket::{Request};
|
||||
use rocket::request::{FromRequest, Outcome};
|
||||
use crate::tokens::{validate_auth_token, validate_session_token};
|
||||
|
||||
pub struct PartialUserInfo {
|
||||
pub user_id: i32,
|
||||
pub created_at: i64,
|
||||
pub email: String,
|
||||
pub has_totp_auth: bool,
|
||||
|
||||
pub session_id: String,
|
||||
pub auth_id: Option<String>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AuthenticationError {
|
||||
MissingToken,
|
||||
InvalidToken(usize),
|
||||
DatabaseError,
|
||||
RequiresTOTP
|
||||
}
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for PartialUserInfo {
|
||||
type Error = AuthenticationError;
|
||||
|
||||
async fn from_request(req: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
let headers = req.headers();
|
||||
|
||||
// make sure the bearer token exists
|
||||
if let Some(authorization) = headers.get_one("Authorization") {
|
||||
// parse bearer token
|
||||
let components = authorization.split(' ').collect::<Vec<&str>>();
|
||||
|
||||
if components.len() != 2 && components.len() != 3 {
|
||||
return Outcome::Failure((Status::Unauthorized, AuthenticationError::MissingToken));
|
||||
}
|
||||
|
||||
if components[0] != "Bearer" {
|
||||
return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(0)));
|
||||
}
|
||||
|
||||
if components.len() == 2 && !components[1].starts_with("st-") {
|
||||
return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(1)));
|
||||
}
|
||||
|
||||
let st: String;
|
||||
let user_id: i64;
|
||||
let at: Option<String>;
|
||||
|
||||
match &components[1][..3] {
|
||||
"st-" => {
|
||||
// validate session token
|
||||
st = components[1].to_string();
|
||||
match validate_session_token(st.clone(), req.rocket().state().unwrap()).await {
|
||||
Ok(uid) => user_id = uid,
|
||||
Err(_) => return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(2)))
|
||||
}
|
||||
},
|
||||
_ => return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(3)))
|
||||
}
|
||||
|
||||
if components.len() == 3 {
|
||||
match &components[2][..3] {
|
||||
"at-" => {
|
||||
// validate auth token
|
||||
at = Some(components[2].to_string());
|
||||
match validate_auth_token(at.clone().unwrap().clone(), st.clone(), req.rocket().state().unwrap()).await {
|
||||
Ok(_) => (),
|
||||
Err(_) => return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(4)))
|
||||
}
|
||||
},
|
||||
_ => return Outcome::Failure((Status::Unauthorized, AuthenticationError::InvalidToken(5)))
|
||||
}
|
||||
} else {
|
||||
at = None;
|
||||
}
|
||||
|
||||
// this user is 100% valid and authenticated, fetch their info
|
||||
|
||||
let user = match sqlx::query!("SELECT * FROM users WHERE id = $1", user_id.clone() as i32).fetch_one(req.rocket().state().unwrap()).await {
|
||||
Ok(u) => u,
|
||||
Err(_) => return Outcome::Failure((Status::InternalServerError, AuthenticationError::DatabaseError))
|
||||
};
|
||||
|
||||
Outcome::Success(PartialUserInfo {
|
||||
user_id: user_id as i32,
|
||||
created_at: user.created_on as i64,
|
||||
email: user.email,
|
||||
has_totp_auth: at.is_some(),
|
||||
session_id: st,
|
||||
auth_id: at,
|
||||
})
|
||||
} else {
|
||||
Outcome::Failure((Status::Unauthorized, AuthenticationError::MissingToken))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TOTPAuthenticatedUserInfo {
|
||||
pub user_id: i32,
|
||||
pub created_at: i64,
|
||||
pub email: String,
|
||||
}
|
||||
#[rocket::async_trait]
|
||||
impl<'r> FromRequest<'r> for TOTPAuthenticatedUserInfo {
|
||||
type Error = AuthenticationError;
|
||||
|
||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> {
|
||||
let userinfo = PartialUserInfo::from_request(request).await;
|
||||
match userinfo {
|
||||
Outcome::Failure(e) => Outcome::Failure(e),
|
||||
Outcome::Forward(f) => Outcome::Forward(f),
|
||||
Outcome::Success(s) => {
|
||||
if s.has_totp_auth {
|
||||
Outcome::Success(Self {
|
||||
user_id: s.user_id,
|
||||
created_at: s.created_at,
|
||||
email: s.email,
|
||||
})
|
||||
} else {
|
||||
Outcome::Failure((Status::Unauthorized, AuthenticationError::RequiresTOTP))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,31 +1,51 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
use std::fs;
|
||||
use log::error;
|
||||
use once_cell::sync::Lazy;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
use serde::Deserialize;
|
||||
use url::Url;
|
||||
pub static CONFIG: Lazy<TrifidConfig> = Lazy::new(|| {
|
||||
let config_str = match fs::read_to_string("/etc/trifid/config.toml") {
|
||||
Ok(str) => str,
|
||||
Err(e) => {
|
||||
error!("Unable to read config file: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TFConfig {
|
||||
pub listen_port: u16,
|
||||
pub db_url: String,
|
||||
pub base: Url,
|
||||
pub web_root: Url,
|
||||
pub magic_links_valid_for: u64,
|
||||
pub session_tokens_valid_for: u64,
|
||||
pub totp_verification_valid_for: u64,
|
||||
pub data_key: String,
|
||||
pub ca_certs_valid_for: u64
|
||||
match toml::from_str(&config_str) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(e) => {
|
||||
error!("Unable to parse config file: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
#[derive(Serialize, Debug, Deserialize)]
|
||||
pub struct TrifidConfig {
|
||||
pub database: TrifidConfigDatabase
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TrifidConfigDatabase {
|
||||
pub url: String,
|
||||
#[serde(default = "max_connections_default")]
|
||||
pub max_connections: u32,
|
||||
#[serde(default = "min_connections_default")]
|
||||
pub min_connections: u32,
|
||||
#[serde(default = "time_defaults")]
|
||||
pub connect_timeout: u64,
|
||||
#[serde(default = "time_defaults")]
|
||||
pub acquire_timeout: u64,
|
||||
#[serde(default = "time_defaults")]
|
||||
pub idle_timeout: u64,
|
||||
#[serde(default = "time_defaults")]
|
||||
pub max_lifetime: u64,
|
||||
#[serde(default = "sqlx_logging_default")]
|
||||
pub sqlx_logging: bool
|
||||
}
|
||||
|
||||
fn max_connections_default() -> u32 { 100 }
|
||||
fn min_connections_default() -> u32 { 5 }
|
||||
fn time_defaults() -> u64 { 8 }
|
||||
fn sqlx_logging_default() -> bool { true }
|
|
@ -1,43 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::error::Error;
|
||||
use aes_gcm::{Aes256Gcm, KeyInit, Nonce};
|
||||
use aes_gcm::aead::{Aead, Payload};
|
||||
use rand::Rng;
|
||||
use rand::rngs::OsRng;
|
||||
use crate::config::TFConfig;
|
||||
|
||||
pub fn get_cipher_from_config(config: &TFConfig) -> Result<Aes256Gcm, Box<dyn Error>> {
|
||||
let key_slice = hex::decode(&config.data_key)?;
|
||||
Ok(Aes256Gcm::new_from_slice(&key_slice)?)
|
||||
}
|
||||
|
||||
pub fn encrypt_with_nonce(plaintext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result<Vec<u8>, aes_gcm::Error> {
|
||||
let nonce = Nonce::from_slice(&nonce);
|
||||
let ciphertext = cipher.encrypt(nonce, plaintext)?;
|
||||
Ok(ciphertext)
|
||||
}
|
||||
|
||||
pub fn decrypt_with_nonce(ciphertext: &[u8], nonce: [u8; 12], cipher: &Aes256Gcm) -> Result<Vec<u8>, aes_gcm::Error> {
|
||||
let nonce = Nonce::from_slice(&nonce);
|
||||
let plaintext = cipher.decrypt(nonce, Payload::from(ciphertext))?;
|
||||
Ok(plaintext)
|
||||
}
|
||||
|
||||
pub fn generate_random_iv() -> [u8; 12] {
|
||||
OsRng.gen()
|
||||
}
|
|
@ -1,15 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
@ -1,98 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::fmt::{Display, Formatter};
|
||||
use crate::format::PEMValidationError::{IncorrectSegmentLength, InvalidBase64Data, MissingStartSentinel};
|
||||
use crate::util::base64decode;
|
||||
|
||||
pub const ED_PUBKEY_START_STR: &str = "-----BEGIN NEBULA ED25519 PUBLIC KEY-----";
|
||||
pub const ED_PUBKEY_END_STR: &str = "-----END NEBULA ED25519 PUBLIC KEY-----";
|
||||
|
||||
pub const DH_PUBKEY_START_STR: &str = "-----BEGIN NEBULA X25519 PUBLIC KEY-----";
|
||||
pub const DH_PUBKEY_END_STR: &str = "-----END NEBULA X25519 PUBLIC KEY-----";
|
||||
|
||||
pub enum PEMValidationError {
|
||||
MissingStartSentinel,
|
||||
InvalidBase64Data,
|
||||
MissingEndSentinel,
|
||||
IncorrectSegmentLength(usize, usize)
|
||||
}
|
||||
impl Display for PEMValidationError {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::MissingEndSentinel => write!(f, "Missing ending sentinel"),
|
||||
Self::MissingStartSentinel => write!(f, "Missing starting sentinel"),
|
||||
Self::InvalidBase64Data => write!(f, "invalid base64 data"),
|
||||
Self::IncorrectSegmentLength(expected, got) => write!(f, "incorrect number of segments, expected {} got {}", expected, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_ed_pubkey_pem(pubkey: &str) -> Result<(), PEMValidationError> {
|
||||
let segments = pubkey.split('\n');
|
||||
let segs = segments.collect::<Vec<&str>>();
|
||||
if segs.len() < 3 {
|
||||
return Err(IncorrectSegmentLength(3, segs.len()))
|
||||
}
|
||||
if segs[0] != ED_PUBKEY_START_STR {
|
||||
return Err(MissingStartSentinel)
|
||||
}
|
||||
if base64decode(segs[1]).is_err() {
|
||||
return Err(InvalidBase64Data)
|
||||
}
|
||||
if segs[2] != ED_PUBKEY_END_STR {
|
||||
return Err(MissingStartSentinel)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_dh_pubkey_pem(pubkey: &str) -> Result<(), PEMValidationError> {
|
||||
let segments = pubkey.split('\n');
|
||||
let segs = segments.collect::<Vec<&str>>();
|
||||
if segs.len() < 3 {
|
||||
return Err(IncorrectSegmentLength(3, segs.len()))
|
||||
}
|
||||
if segs[0] != DH_PUBKEY_START_STR {
|
||||
return Err(MissingStartSentinel)
|
||||
}
|
||||
if base64decode(segs[1]).is_err() {
|
||||
return Err(InvalidBase64Data)
|
||||
}
|
||||
if segs[2] != DH_PUBKEY_END_STR {
|
||||
return Err(MissingStartSentinel)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_ed_pubkey_base64(pubkey: &str) -> Result<(), PEMValidationError> {
|
||||
match base64decode(pubkey) {
|
||||
Ok(k) => validate_ed_pubkey_pem(match std::str::from_utf8(k.as_ref()) {
|
||||
Ok(k) => k,
|
||||
Err(_) => return Err(InvalidBase64Data)
|
||||
}),
|
||||
Err(_) => Err(InvalidBase64Data)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate_dh_pubkey_base64(pubkey: &str) -> Result<(), PEMValidationError> {
|
||||
match base64decode(pubkey) {
|
||||
Ok(k) => validate_dh_pubkey_pem(match std::str::from_utf8(k.as_ref()) {
|
||||
Ok(k) => k,
|
||||
Err(_) => return Err(InvalidBase64Data)
|
||||
}),
|
||||
Err(_) => Err(InvalidBase64Data)
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::error::Error;
|
||||
use sqlx::PgPool;
|
||||
|
||||
pub async fn kv_get<'a>(key: &'a str, db: &PgPool) -> Result<Option<String>, Box<dyn Error>> {
|
||||
let res = sqlx::query!("SELECT datavalue FROM cacheddata WHERE datakey = $1", key).fetch_optional(db).await?;
|
||||
Ok(res.map(|i| i.datavalue))
|
||||
}
|
||||
|
||||
pub async fn kv_set(key: &str, value: &str, db: &PgPool) -> Result<(), Box<dyn Error>> {
|
||||
sqlx::query!("INSERT INTO cacheddata (datakey, datavalue) VALUES ($2, $1) ON CONFLICT (datakey) DO UPDATE SET datavalue = $1", value, key).execute(db).await?;
|
||||
Ok(())
|
||||
}
|
|
@ -1,213 +1,35 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
extern crate core;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use dotenvy::dotenv;
|
||||
use log::{error, info};
|
||||
use rocket::{catchers, Request, Response, routes};
|
||||
use rocket::fairing::{Fairing, Info, Kind};
|
||||
use rocket::http::Header;
|
||||
use sha2::Sha256;
|
||||
use sqlx::migrate::Migrator;
|
||||
use sqlx::postgres::PgPoolOptions;
|
||||
use crate::config::TFConfig;
|
||||
use crate::kv::{kv_get, kv_set};
|
||||
use sha2::Digest;
|
||||
use std::time::Duration;
|
||||
use actix_web::{App, HttpResponse, HttpServer, post, web::{Data, Json, JsonConfig}};
|
||||
use log::{error, info, Level};
|
||||
use sea_orm::{ConnectOptions, Database, DatabaseConnection};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::config::CONFIG;
|
||||
|
||||
|
||||
pub mod format;
|
||||
pub mod util;
|
||||
pub mod db;
|
||||
pub mod config;
|
||||
pub mod tokens;
|
||||
pub mod routes;
|
||||
pub mod auth;
|
||||
pub mod crypto;
|
||||
pub mod org;
|
||||
pub mod kv;
|
||||
pub mod role;
|
||||
|
||||
static MIGRATOR: Migrator = sqlx::migrate!();
|
||||
|
||||
pub struct CORS;
|
||||
|
||||
#[rocket::async_trait]
|
||||
impl Fairing for CORS {
|
||||
fn info(&self) -> Info {
|
||||
Info {
|
||||
name: "Add CORS headers to responses",
|
||||
kind: Kind::Response,
|
||||
}
|
||||
}
|
||||
|
||||
async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
|
||||
response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
|
||||
response.set_header(Header::new("Access-Control-Allow-Methods", "POST, GET, PATCH, OPTIONS"));
|
||||
response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
|
||||
response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
|
||||
}
|
||||
}
|
||||
|
||||
#[rocket::main]
|
||||
#[actix_web::main]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
let _ = rocket::build();
|
||||
simple_logger::init_with_level(Level::Debug).unwrap();
|
||||
|
||||
info!("[tfapi] loading config");
|
||||
info!("Connecting to database at {}...", CONFIG.database.url);
|
||||
|
||||
let _ = dotenv();
|
||||
let mut opt = ConnectOptions::new(CONFIG.database.url.clone());
|
||||
opt.max_connections(CONFIG.database.max_connections)
|
||||
.min_connections(CONFIG.database.min_connections)
|
||||
.connect_timeout(Duration::from_secs(CONFIG.database.connect_timeout))
|
||||
.acquire_timeout(Duration::from_secs(CONFIG.database.acquire_timeout))
|
||||
.idle_timeout(Duration::from_secs(CONFIG.database.idle_timeout))
|
||||
.max_lifetime(Duration::from_secs(CONFIG.database.max_lifetime))
|
||||
.sqlx_logging(CONFIG.database.sqlx_logging)
|
||||
.sqlx_logging_level(log::LevelFilter::Info);
|
||||
|
||||
if std::env::var("CONFIG_FILE").is_err() && !Path::new("config.toml").exists() {
|
||||
error!("[tfapi] fatal: the environment variable CONFIG_FILE is not set");
|
||||
error!("[tfapi] help: try creating a .env file that sets it");
|
||||
error!("[tfapi] help: or, create a file config.toml with your config, as it is loaded automatically");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let db = Database::connect(opt).await?;
|
||||
|
||||
let config_file = if Path::new("config.toml").exists() {
|
||||
"config.toml".to_string()
|
||||
} else {
|
||||
std::env::var("CONFIG_FILE").unwrap()
|
||||
};
|
||||
|
||||
let config_data = match fs::read_to_string(&config_file) {
|
||||
Ok(d) => d,
|
||||
Err(e) => {
|
||||
error!("[tfapi] fatal: unable to read config from {}", config_file);
|
||||
error!("[tfapi] fatal: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
let config: TFConfig = match toml::from_str(&config_data) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
error!("[tfapi] fatal: unable to parse config from {}", config_file);
|
||||
error!("[tfapi] fatal: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
info!("[tfapi] connecting to database pool");
|
||||
|
||||
let pool = match PgPoolOptions::new().max_connections(5).connect(&config.db_url).await {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
error!("[tfapi] fatal: unable to connect to database pool");
|
||||
error!("[tfapi] fatal: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
info!("[tfapi] running database migrations");
|
||||
|
||||
MIGRATOR.run(&pool).await?;
|
||||
|
||||
info!("[tfapi] verifying encryption key");
|
||||
|
||||
let kv_hash = kv_get("pmk_hash", &pool).await.expect("Unable to get pmk hash from kv store");
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(config.data_key.as_bytes());
|
||||
let config_hash = hex::encode(hasher.finalize());
|
||||
if let Some(k_hash) = kv_hash {
|
||||
if config_hash != k_hash {
|
||||
error!("[tfapi] fatal: instance master key does not match key used to encrypt data");
|
||||
error!("[tfapi] fatal: datastore was encrypted with keyid {k_hash}");
|
||||
error!("[tfapi] fatal: the key in your config has the keyid {config_hash}");
|
||||
error!("[tfapi] fatal: you probably changed data_key. please return it to it's original value");
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
info!("[tfapi] data keyid is {config_hash}");
|
||||
}
|
||||
} else {
|
||||
info!("[tfapi] detected first run");
|
||||
info!("[tfapi] welcome to trifid!");
|
||||
info!("[tfapi] data keyid is {config_hash}");
|
||||
|
||||
if let Err(e) = kv_set("pmk_hash", config_hash.as_str(), &pool).await {
|
||||
error!("[tfapi] fatal: unable to set pmk_hash in kv store");
|
||||
error!("[tfapi] fatal: the database returned the following error:");
|
||||
error!("[tfapi] fatal: {e}");
|
||||
std::process::exit(1);
|
||||
} else {
|
||||
info!("[tfapi] configured instance information in kv store");
|
||||
}
|
||||
}
|
||||
|
||||
info!("[tfapi] building rocket config");
|
||||
|
||||
let figment = rocket::Config::figment().merge(("port", config.listen_port));
|
||||
|
||||
let _ = rocket::custom(figment)
|
||||
.mount("/", routes![
|
||||
crate::routes::v1::auth::magic_link::magiclink_request,
|
||||
crate::routes::v1::auth::magic_link::options,
|
||||
crate::routes::v1::signup::signup_request,
|
||||
crate::routes::v1::signup::options,
|
||||
crate::routes::v1::auth::verify_magic_link::verify_magic_link,
|
||||
crate::routes::v1::auth::verify_magic_link::options,
|
||||
crate::routes::v1::totp_authenticators::totp_authenticators_request,
|
||||
crate::routes::v1::totp_authenticators::options,
|
||||
crate::routes::v1::verify_totp_authenticator::verify_totp_authenticator_request,
|
||||
crate::routes::v1::verify_totp_authenticator::options,
|
||||
crate::routes::v1::auth::totp::totp_request,
|
||||
crate::routes::v1::auth::totp::options,
|
||||
crate::routes::v1::auth::check_session::check_session,
|
||||
crate::routes::v1::auth::check_session::check_session_auth,
|
||||
crate::routes::v1::auth::check_session::options,
|
||||
crate::routes::v1::auth::check_session::options_auth,
|
||||
crate::routes::v2::whoami::whoami_request,
|
||||
crate::routes::v2::whoami::options,
|
||||
|
||||
crate::routes::v1::organization::options,
|
||||
crate::routes::v1::organization::orgidoptions,
|
||||
crate::routes::v1::organization::orginfo_req,
|
||||
crate::routes::v1::organization::orglist_req,
|
||||
crate::routes::v1::organization::create_org,
|
||||
crate::routes::v1::user::get_user,
|
||||
crate::routes::v1::user::options,
|
||||
crate::routes::v1::organization::createorgoptions,
|
||||
crate::routes::v1::ca::get_cas_for_org,
|
||||
crate::routes::v1::ca::options,
|
||||
crate::routes::v1::roles::get_roles,
|
||||
crate::routes::v1::roles::options,
|
||||
crate::routes::v1::roles::options_roleadd,
|
||||
crate::routes::v1::roles::role_add
|
||||
])
|
||||
.register("/", catchers![
|
||||
crate::routes::handler_400,
|
||||
crate::routes::handler_401,
|
||||
crate::routes::handler_403,
|
||||
crate::routes::handler_404,
|
||||
crate::routes::handler_422,
|
||||
|
||||
crate::routes::handler_500,
|
||||
crate::routes::handler_501,
|
||||
crate::routes::handler_502,
|
||||
crate::routes::handler_503,
|
||||
crate::routes::handler_504,
|
||||
crate::routes::handler_505,
|
||||
])
|
||||
.attach(CORS)
|
||||
.manage(pool)
|
||||
.manage(config)
|
||||
.launch().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use rocket::form::validate::Contains;
|
||||
use sqlx::PgPool;
|
||||
use trifid_pki::ca::NebulaCAPool;
|
||||
|
||||
pub async fn get_org_by_owner_id(user: i32, db: &PgPool) -> Result<Option<i32>, Box<dyn Error>> {
|
||||
|
||||
Ok(sqlx::query!("SELECT id FROM organizations WHERE owner = $1", user).fetch_optional(db).await?.map(|r| r.id))
|
||||
}
|
||||
|
||||
pub async fn get_orgs_by_assoc_id(user: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
|
||||
let res: Vec<_> = sqlx::query!("SELECT org_id FROM organization_authorized_users WHERE user_id = $1", user).fetch_all(db).await?;
|
||||
|
||||
let mut ret = vec![];
|
||||
|
||||
for i in res {
|
||||
ret.push(i.org_id);
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub async fn get_users_by_assoc_org(org: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
|
||||
let res: Vec<_> = sqlx::query!("SELECT user_id FROM organization_authorized_users WHERE org_id = $1", org).fetch_all(db).await?;
|
||||
|
||||
let mut ret = vec![];
|
||||
|
||||
for i in res {
|
||||
ret.push(i.user_id);
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
pub async fn get_associated_orgs(user: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
|
||||
let mut assoc_orgs = vec![];
|
||||
|
||||
if let Some(owned_org) = get_org_by_owner_id(user, db).await? {
|
||||
assoc_orgs.push(owned_org);
|
||||
}
|
||||
|
||||
assoc_orgs.append(&mut get_orgs_by_assoc_id(user, db).await?);
|
||||
|
||||
Ok(assoc_orgs)
|
||||
}
|
||||
|
||||
pub async fn user_has_org_assoc(user: i32, org: i32, db: &PgPool) -> Result<bool, Box<dyn Error>> {
|
||||
let associated_orgs = get_associated_orgs(user, db).await?;
|
||||
|
||||
Ok(associated_orgs.contains(org))
|
||||
}
|
||||
|
||||
pub async fn get_org_ca_pool(org: i32, db: &PgPool) -> Result<NebulaCAPool, Box<dyn Error>> {
|
||||
// get CAPool PEM from db
|
||||
let pem = sqlx::query!("SELECT ca_crt FROM organizations WHERE id = $1", org).fetch_one(db).await?.ca_crt;
|
||||
|
||||
let ca_pool = NebulaCAPool::new_from_pem(&hex::decode(pem)?)?;
|
||||
|
||||
Ok(ca_pool)
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::error::Error;
|
||||
use sqlx::PgPool;
|
||||
use serde::{Serialize, Deserialize};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[repr(i32)]
|
||||
pub enum Protocol {
|
||||
Any = 0,
|
||||
TCP = 1,
|
||||
UDP = 2,
|
||||
ICMP = 3
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum AllowFrom {
|
||||
Anyone,
|
||||
SpecificRole(i32)
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct FirewallRule {
|
||||
pub id: i32,
|
||||
pub protocol: Protocol,
|
||||
pub port_start: u16,
|
||||
pub port_end: u16,
|
||||
pub allow_from: AllowFrom,
|
||||
pub description: String
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct Role {
|
||||
pub id: i32,
|
||||
pub org_id: i32,
|
||||
pub name: String,
|
||||
pub description: String,
|
||||
pub firewall_rules: Vec<FirewallRule>
|
||||
}
|
||||
|
||||
pub async fn get_role(role_id: i32, db: &PgPool) -> Result<Option<Role>, Box<dyn Error>> {
|
||||
let query_result: Option<_> = sqlx::query!("SELECT * FROM roles WHERE id = $1", role_id).fetch_optional(db).await?;
|
||||
|
||||
if let Some(res) = query_result {
|
||||
// get all firewall rules
|
||||
let rules_res = sqlx::query!("SELECT * FROM roles_firewall_rules WHERE role = $1", Some(role_id)).fetch_all(db).await?;
|
||||
|
||||
let mut rules = vec![];
|
||||
|
||||
for rule in rules_res {
|
||||
rules.push(FirewallRule {
|
||||
id: rule.id,
|
||||
protocol: match rule.protocol {
|
||||
0 => Protocol::Any,
|
||||
1 => Protocol::TCP,
|
||||
2 => Protocol::UDP,
|
||||
3 => Protocol::ICMP,
|
||||
_ => return Err(format!("invalid protocol on a firewall rule {}", rule.id).into())
|
||||
},
|
||||
port_start: rule.port_range_start as u16,
|
||||
port_end: rule.port_range_end as u16,
|
||||
allow_from: match rule.allow_from {
|
||||
-1 => AllowFrom::Anyone,
|
||||
_ => AllowFrom::SpecificRole(rule.allow_from)
|
||||
},
|
||||
description: rule.description,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(Some(Role {
|
||||
id: role_id,
|
||||
org_id: res.org,
|
||||
name: res.name,
|
||||
description: res.description,
|
||||
firewall_rules: rules,
|
||||
}))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_role_ids_for_ca(org_id: i32, db: &PgPool) -> Result<Vec<i32>, Box<dyn Error>> {
|
||||
Ok(sqlx::query!("SELECT id FROM roles WHERE org = $1", org_id).fetch_all(db).await?.iter().map(|r| r.id).collect())
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod v1;
|
||||
pub mod v2;
|
||||
|
||||
use rocket::catch;
|
||||
use serde::{Serialize};
|
||||
use rocket::http::Status;
|
||||
|
||||
pub const ERR_MSG_MALFORMED_REQUEST: &str = "unable to parse the request body - is it valid JSON, using correct types?";
|
||||
pub const ERR_MSG_MALFORMED_REQUEST_CODE: &str = "ERR_MALFORMED_REQUEST";
|
||||
|
||||
/*
|
||||
TODO:
|
||||
/v1/auth/magic-link [done]
|
||||
/v1/auth/totp [done]
|
||||
/v1/auth/verify-magic-link [done]
|
||||
/v1/hosts/host-{id}/enrollment-code
|
||||
/v1/hosts/host-{id}/enrollment-code-check
|
||||
/v1/hosts/host-{id}
|
||||
/v1/roles/role-{id}
|
||||
/v1/feature-flags
|
||||
/v1/hosts
|
||||
/v1/networks
|
||||
/v1/roles
|
||||
/v1/signup [done]
|
||||
/v1/totp-authenticators [done]
|
||||
/v1/verify-totp-authenticator [done]
|
||||
/v1/dnclient
|
||||
/v2/enroll
|
||||
/v2/whoami [in-progress]
|
||||
*/
|
||||
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct APIError {
|
||||
errors: Vec<APIErrorSingular>
|
||||
}
|
||||
#[derive(Serialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct APIErrorSingular {
|
||||
code: String,
|
||||
message: String
|
||||
}
|
||||
|
||||
macro_rules! error_handler {
|
||||
($code: expr, $err: expr, $msg: expr) => {
|
||||
::paste::paste! {
|
||||
#[catch($code)]
|
||||
pub fn [<handler_ $code>]() -> (Status, String) {
|
||||
(Status::from_code($code).unwrap(), format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", $err, $msg))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
error_handler!(400, "ERR_MALFORMED_REQUEST", "unable to parse the request body, is it properly formatted?");
|
||||
error_handler!(401, "ERR_AUTHENTICATION_REQUIRED", "this endpoint requires authentication but it was not provided");
|
||||
error_handler!(403, "ERR_UNAUTHORIZED", "authorization was provided but it is expired or invalid");
|
||||
error_handler!(404, "ERR_NOT_FOUND", "resource not found");
|
||||
error_handler!(405, "ERR_METHOD_NOT_ALLOWED", "method not allowed for this endpoint");
|
||||
error_handler!(422, "ERR_MALFORMED_REQUEST", "unable to parse the request body, is it properly formatted?");
|
||||
|
||||
error_handler!(500, "ERR_QL_QUERY_FAILED", "graphql query timed out");
|
||||
error_handler!(501, "ERR_NOT_IMPLEMENTED", "query not supported by this version of graphql");
|
||||
error_handler!(502, "ERR_PROXY_ERR", "servers under load, please try again later");
|
||||
error_handler!(503, "ERR_SERVER_OVERLOADED", "servers under load, please try again later");
|
||||
error_handler!(504, "ERR_PROXY_TIMEOUT", "servers under load, please try again later");
|
||||
error_handler!(505, "ERR_CLIENT_UNSUPPORTED", "your version of dnclient is out of date, please update");
|
|
@ -1,47 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use rocket::{post, options};
|
||||
use crate::auth::{PartialUserInfo, TOTPAuthenticatedUserInfo};
|
||||
|
||||
/*
|
||||
These endpoints do not return any actual data, and are used purely to check auth tokens
|
||||
by the client code.
|
||||
Since PartialUserInfo implements FromRequest, and will error out the req even before
|
||||
it gets to our handler if the auth is invalid, these reqs just have to have
|
||||
it as a param. They therefore don't need to s
|
||||
*/
|
||||
|
||||
#[options("/v1/auth/check_session")]
|
||||
pub async fn options() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
#[post("/v1/auth/check_session")]
|
||||
pub async fn check_session(_user: PartialUserInfo) -> &'static str {
|
||||
"ok"
|
||||
}
|
||||
|
||||
#[options("/v1/auth/check_auth")]
|
||||
pub async fn options_auth() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
|
||||
#[post("/v1/auth/check_auth")]
|
||||
pub async fn check_session_auth(_user: TOTPAuthenticatedUserInfo) -> &'static str {
|
||||
"ok"
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use rocket::{post, State};
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rocket::http::{ContentType, Status};
|
||||
use sqlx::PgPool;
|
||||
use crate::config::TFConfig;
|
||||
use crate::tokens::send_magic_link;
|
||||
use rocket::options;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct MagicLinkRequest {
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct MagicLinkResponseMetadata {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct MagicLinkResponse {
|
||||
pub data: Option<String>,
|
||||
pub metadata: MagicLinkResponseMetadata,
|
||||
}
|
||||
|
||||
|
||||
#[options("/v1/auth/magic-link")]
|
||||
pub async fn options() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
#[post("/v1/auth/magic-link", data = "<req>")]
|
||||
pub async fn magiclink_request(req: Json<MagicLinkRequest>, pool: &State<PgPool>, config: &State<TFConfig>) -> Result<(ContentType, Json<MagicLinkResponse>), (Status, String)> {
|
||||
// figure out if the user already exists
|
||||
let mut id = -1;
|
||||
match sqlx::query!("SELECT id FROM users WHERE email = $1", req.email.clone()).fetch_optional(pool.inner()).await {
|
||||
Ok(res) => if let Some(r) = res { id = r.id as i64 },
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))
|
||||
}
|
||||
}
|
||||
|
||||
if id == -1 {
|
||||
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_UNAUTHORIZED", "authorization was provided but it is expired or invalid")))
|
||||
}
|
||||
|
||||
// send magic link to email
|
||||
match send_magic_link(id, req.email.clone(), pool.inner(), config.inner()).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))
|
||||
}
|
||||
};
|
||||
|
||||
// this endpoint doesn't actually ever return an error? it will send you the magic link no matter what
|
||||
// this appears to do the exact same thing as /v1/auth/magic-link, but it doesn't check if you have an account (magic-link does)
|
||||
Ok((ContentType::JSON, Json(MagicLinkResponse {
|
||||
data: None,
|
||||
metadata: MagicLinkResponseMetadata {},
|
||||
})))
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod verify_magic_link;
|
||||
pub mod magic_link;
|
||||
pub mod totp;
|
||||
pub mod check_session;
|
|
@ -1,89 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use rocket::http::{ContentType, Status};
|
||||
use rocket::serde::json::Json;
|
||||
use crate::auth::PartialUserInfo;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rocket::{post, State};
|
||||
use sqlx::PgPool;
|
||||
use rocket::options;
|
||||
use crate::tokens::{generate_auth_token, get_totpmachine, user_has_totp};
|
||||
|
||||
pub const TOTP_GENERIC_UNAUTHORIZED_ERROR: &str = "{\"errors\":[{\"code\":\"ERR_INVALID_TOTP_CODE\",\"message\":\"invalid TOTP code (maybe it expired?)\",\"path\":\"code\"}]}";
|
||||
pub const TOTP_NO_TOTP_ERROR: &str = "{\"errors\":[{\"code\":\"ERR_NO_TOTP\",\"message\":\"logged-in user does not have totp enabled\",\"path\":\"code\"}]}";
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct TotpRequest {
|
||||
pub code: String
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct TotpResponseData {
|
||||
#[serde(rename = "authToken")]
|
||||
auth_token: String
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct TotpResponseMetadata {
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct TotpResponse {
|
||||
data: TotpResponseData,
|
||||
metadata: TotpResponseMetadata
|
||||
}
|
||||
|
||||
#[options("/v1/auth/totp")]
|
||||
pub async fn options() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
|
||||
#[post("/v1/auth/totp", data = "<req>")]
|
||||
pub async fn totp_request(req: Json<TotpRequest>, user: PartialUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<TotpResponse>), (Status, String)> {
|
||||
if !match user_has_totp(user.user_id, db.inner()).await {
|
||||
Ok(b) => b,
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e)))
|
||||
} {
|
||||
return Err((Status::UnprocessableEntity, TOTP_NO_TOTP_ERROR.to_string()))
|
||||
}
|
||||
|
||||
if user.has_totp_auth {
|
||||
return Err((Status::BadRequest, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_TOTP_ALREADY_AUTHED", "user already has valid totp authentication")))
|
||||
}
|
||||
|
||||
let totpmachine = match get_totpmachine(user.user_id, db.inner()).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e)))
|
||||
}
|
||||
};
|
||||
|
||||
if !totpmachine.check_current(&req.0.code).unwrap_or(false) {
|
||||
return Err((Status::Unauthorized, TOTP_GENERIC_UNAUTHORIZED_ERROR.to_string()))
|
||||
}
|
||||
|
||||
Ok((ContentType::JSON, Json(TotpResponse {
|
||||
data: TotpResponseData { auth_token: match generate_auth_token(user.user_id as i64, user.session_id, db.inner()).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => { return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e))) }
|
||||
} },
|
||||
metadata: TotpResponseMetadata {},
|
||||
})))
|
||||
}
|
|
@ -1,91 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use rocket::http::{ContentType, Status};
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rocket::{post, State};
|
||||
use sqlx::PgPool;
|
||||
use crate::config::TFConfig;
|
||||
use crate::tokens::generate_session_token;
|
||||
use rocket::options;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct VerifyMagicLinkRequest {
|
||||
#[serde(rename = "magicLinkToken")]
|
||||
pub magic_link_token: String,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyMagicLinkResponseMetadata {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyMagicLinkResponseData {
|
||||
#[serde(rename = "sessionToken")]
|
||||
pub session_token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyMagicLinkResponse {
|
||||
pub data: VerifyMagicLinkResponseData,
|
||||
pub metadata: VerifyMagicLinkResponseMetadata,
|
||||
}
|
||||
|
||||
#[options("/v1/auth/verify-magic-link")]
|
||||
pub async fn options() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
|
||||
#[post("/v1/auth/verify-magic-link", data = "<req>")]
|
||||
pub async fn verify_magic_link(req: Json<VerifyMagicLinkRequest>, db: &State<PgPool>, config: &State<TFConfig>) -> Result<(ContentType, Json<VerifyMagicLinkResponse>), (Status, String)> {
|
||||
// get the current time to check if the token is expired
|
||||
let (user_id, expired_at) = match sqlx::query!("SELECT user_id, expires_on FROM magic_links WHERE id = $1", req.0.magic_link_token).fetch_one(db.inner()).await {
|
||||
Ok(row) => (row.user_id, row.expires_on),
|
||||
Err(e) => {
|
||||
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNAUTHORIZED", "this token is invalid", e)))
|
||||
}
|
||||
};
|
||||
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32;
|
||||
println!("expired on {}, currently {}", expired_at, current_time);
|
||||
if expired_at < current_time {
|
||||
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_UNAUTHORIZED", "valid authorization was provided but it is expired")))
|
||||
}
|
||||
|
||||
// generate session token
|
||||
let token = match generate_session_token(user_id as i64, db.inner(), config.inner()).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e)))
|
||||
}
|
||||
};
|
||||
|
||||
// delete the token
|
||||
match sqlx::query!("DELETE FROM magic_links WHERE id = $1", req.0.magic_link_token).execute(db.inner()).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e)))
|
||||
}
|
||||
}
|
||||
|
||||
Ok((ContentType::JSON, Json(VerifyMagicLinkResponse {
|
||||
data: VerifyMagicLinkResponseData { session_token: token },
|
||||
metadata: VerifyMagicLinkResponseMetadata {},
|
||||
})))
|
||||
}
|
|
@ -1,85 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
use rocket::{options, get, State};
|
||||
use rocket::http::{ContentType, Status};
|
||||
use rocket::serde::json::Json;
|
||||
use sqlx::PgPool;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::auth::TOTPAuthenticatedUserInfo;
|
||||
use crate::org::{get_associated_orgs, get_org_ca_pool};
|
||||
|
||||
#[options("/v1/org/<_id>/ca")]
|
||||
pub fn options(_id: i32) -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct CaList {
|
||||
pub trusted_cas: Vec<CA>,
|
||||
pub blocklisted_certs: Vec<String>
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct CA {
|
||||
pub fingerprint: String,
|
||||
pub cert: String
|
||||
}
|
||||
|
||||
#[get("/v1/org/<id>/ca")]
|
||||
pub async fn get_cas_for_org(id: i32, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<CaList>), (Status, String)> {
|
||||
let associated_orgs = match get_associated_orgs(user.user_id, db.inner()).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_DB_QUERY_FAILED", "an error occurred while running the database query", e)))
|
||||
};
|
||||
|
||||
if !associated_orgs.contains(&id) {
|
||||
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_NOT_YOUR_ORG", "you are not authorized to view details of this org")))
|
||||
}
|
||||
|
||||
let ca_pool = match get_org_ca_pool(id, db.inner()).await {
|
||||
Ok(pool) => pool,
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"ERR_QL_QUERY_FAILED\",\"message\":\"unable to load certificates from database - {}\"}}]}}", e)));
|
||||
}
|
||||
};
|
||||
|
||||
let mut trusted_cas = vec![];
|
||||
|
||||
for (fingerprint, cert) in ca_pool.cas {
|
||||
trusted_cas.push(CA {
|
||||
fingerprint,
|
||||
cert: match cert.serialize_to_pem() {
|
||||
Ok(pem) => match String::from_utf8(pem) {
|
||||
Ok(str) => str,
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"ERR_QL_QUERY_FAILED\",\"message\":\"unable to encode one of the serialized certificates - {}\"}}]}}", e)))
|
||||
},
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"ERR_QL_QUERY_FAILED\",\"message\":\"unable to serialize one of the certificates - {}\"}}]}}", e)));
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Ok((ContentType::JSON, Json(CaList {
|
||||
trusted_cas,
|
||||
blocklisted_certs: ca_pool.cert_blocklist,
|
||||
})))
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod auth;
|
||||
pub mod signup;
|
||||
|
||||
pub mod totp_authenticators;
|
||||
pub mod verify_totp_authenticator;
|
||||
pub mod organization;
|
||||
pub mod user;
|
||||
pub mod ca;
|
||||
pub mod roles;
|
|
@ -1,190 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::time::{Duration, SystemTime};
|
||||
use ipnet::Ipv4Net;
|
||||
use rocket::{get, post, options, State, error};
|
||||
use rocket::http::{ContentType, Status};
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sqlx::PgPool;
|
||||
use trifid_pki::cert::{NebulaCertificate, NebulaCertificateDetails, serialize_ed25519_private};
|
||||
use trifid_pki::ed25519_dalek::{SigningKey};
|
||||
use trifid_pki::rand_core::OsRng;
|
||||
use crate::auth::TOTPAuthenticatedUserInfo;
|
||||
use crate::config::TFConfig;
|
||||
use crate::crypto::{encrypt_with_nonce, generate_random_iv, get_cipher_from_config};
|
||||
use crate::org::{get_associated_orgs, get_org_by_owner_id, get_users_by_assoc_org, user_has_org_assoc};
|
||||
|
||||
#[options("/v1/orgs")]
|
||||
pub fn options() -> &'static str {
|
||||
""
|
||||
}
|
||||
#[options("/v1/org/<_id>")]
|
||||
pub fn orgidoptions(_id: i32) -> &'static str {
|
||||
""
|
||||
}
|
||||
#[options("/v1/org")]
|
||||
pub fn createorgoptions() -> &'static str {""}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct OrglistStruct {
|
||||
pub org_ids: Vec<i32>
|
||||
}
|
||||
|
||||
|
||||
#[get("/v1/orgs")]
|
||||
pub async fn orglist_req(user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<OrglistStruct>), (Status, String)> {
|
||||
// this endpoint lists the associated organizations this user has access to
|
||||
let associated_orgs = match get_associated_orgs(user.user_id, db.inner()).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_DB_QUERY_FAILED", "an error occurred while running the database query", e)))
|
||||
};
|
||||
|
||||
Ok((ContentType::JSON, Json(OrglistStruct {
|
||||
org_ids: associated_orgs
|
||||
})))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct OrginfoStruct {
|
||||
pub org_id: i32,
|
||||
pub owner_id: i32,
|
||||
pub ca_crt: String,
|
||||
pub authorized_users: Vec<i32>
|
||||
}
|
||||
|
||||
#[get("/v1/org/<orgid>")]
|
||||
pub async fn orginfo_req(orgid: i32, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<OrginfoStruct>), (Status, String)> {
|
||||
if !user_has_org_assoc(user.user_id, orgid, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? {
|
||||
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org")));
|
||||
}
|
||||
|
||||
let org = sqlx::query!("SELECT id, owner, ca_crt FROM organizations WHERE id = $1", orgid).fetch_one(db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?;
|
||||
let authorized_users = get_users_by_assoc_org(orgid, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?;
|
||||
|
||||
Ok((ContentType::JSON, Json(
|
||||
OrginfoStruct {
|
||||
org_id: orgid,
|
||||
owner_id: org.owner,
|
||||
ca_crt: org.ca_crt,
|
||||
authorized_users,
|
||||
}
|
||||
)))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct CreateCARequest {
|
||||
pub ip_ranges: Vec<Ipv4Net>,
|
||||
pub subnet_ranges: Vec<Ipv4Net>,
|
||||
pub groups: Vec<String>
|
||||
}
|
||||
|
||||
#[post("/v1/org", data = "<req>")]
|
||||
pub async fn create_org(req: Json<CreateCARequest>, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>, config: &State<TFConfig>) -> Result<(ContentType, Json<OrginfoStruct>), (Status, String)> {
|
||||
if get_org_by_owner_id(user.user_id, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?.is_some() {
|
||||
return Err((Status::Conflict, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_USER_OWNS_ORG", "a user can only own one organization at a time")))
|
||||
}
|
||||
|
||||
// Generate the CA keypair
|
||||
let private_key = SigningKey::generate(&mut OsRng);
|
||||
let public_key = private_key.verifying_key();
|
||||
|
||||
// Create the CA certificate
|
||||
let mut ca_cert = NebulaCertificate {
|
||||
details: NebulaCertificateDetails {
|
||||
name: format!("{}'s Organization - Root Signing CA", user.email),
|
||||
ips: req.ip_ranges.clone(),
|
||||
subnets: req.subnet_ranges.clone(),
|
||||
groups: req.groups.clone(),
|
||||
not_before: SystemTime::now(),
|
||||
not_after: SystemTime::now() + Duration::from_secs(config.ca_certs_valid_for),
|
||||
public_key: public_key.to_bytes(),
|
||||
is_ca: true,
|
||||
issuer: "".to_string(), // This is a self-signed certificate! There is no issuer present
|
||||
},
|
||||
signature: vec![],
|
||||
};
|
||||
// Self-sign the CA certificate
|
||||
match ca_cert.sign(&private_key) {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
error!("[tfapi] security: certificate signature error: {}", e);
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_CERT_SIGN_ERROR", "there was an error generating the CA certificate, please try again later")))
|
||||
}
|
||||
}
|
||||
|
||||
// PEM-encode the CA key
|
||||
let ca_key_pem = serialize_ed25519_private(&private_key.to_keypair_bytes());
|
||||
// PEM-encode the CA cert
|
||||
let ca_cert_pem = match ca_cert.serialize_to_pem() {
|
||||
Ok(pem) => pem,
|
||||
Err(e) => {
|
||||
error!("[tfapi] security: certificate encoding error: {}", e);
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_CERT_ENCODE_ERROR", "there was an error encoding the CA certificate, please try again later")))
|
||||
}
|
||||
};
|
||||
|
||||
// generate an AES iv to use for key encryption
|
||||
let iv = generate_random_iv();
|
||||
let iv_hex = hex::encode(iv);
|
||||
|
||||
let owner_id = user.user_id;
|
||||
let cipher = match get_cipher_from_config(config) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_CRYPTOGRAPHY_CREATE_CIPHER", "Unable to build cipher construct, please try again later", e)));
|
||||
}
|
||||
};
|
||||
let ca_key = match encrypt_with_nonce(&ca_key_pem, iv, &cipher) {
|
||||
Ok(key) => hex::encode(key),
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_CRYPTOGRAPHY_ENCRYPT_KEY", "Unable to build cipher construct, please try again later", e)));
|
||||
}
|
||||
};
|
||||
let ca_crt = hex::encode(ca_cert_pem);
|
||||
|
||||
let result = sqlx::query!("INSERT INTO organizations (owner, ca_key, ca_crt, iv) VALUES ($1, $2, $3, $4) RETURNING id", owner_id, ca_key, ca_crt, iv_hex).fetch_one(db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))?;
|
||||
|
||||
// last step: create a default role to allow pings from all hosts
|
||||
let role_id = match sqlx::query!("INSERT INTO roles (org, name, description) VALUES ($1, 'Default', 'Allow pings from other hosts. Default role for new hosts.') RETURNING id", result.id).fetch_one(db.inner()).await {
|
||||
Ok(r) => r.id,
|
||||
Err(e) => {
|
||||
error!("[tfapi] dberror: {}", e);
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_INTERNAL_SERVER_ERROR", "Unable to create default role")));
|
||||
}
|
||||
};
|
||||
|
||||
match sqlx::query!("INSERT INTO roles_firewall_rules (role, protocol, port_range_start, port_range_end, allow_from, description) VALUES ($1, 3, 1, 1, -1, 'Allow pings from anyone on the network')", role_id).execute(db.inner()).await {
|
||||
Ok(_) => {},
|
||||
Err(e) => {
|
||||
error!("[tfapi] dberror: {} inserting on roleid {}", e, role_id);
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_INTERNAL_SERVER_ERROR", "Unable to create default role firewall rules")));
|
||||
}
|
||||
}
|
||||
|
||||
Ok((ContentType::JSON, Json(
|
||||
OrginfoStruct {
|
||||
org_id: result.id,
|
||||
owner_id,
|
||||
ca_crt,
|
||||
authorized_users: vec![owner_id],
|
||||
}
|
||||
)))
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use log::error;
|
||||
use rocket::{options, get, State, post};
|
||||
use rocket::http::{ContentType, Status};
|
||||
use rocket::serde::json::Json;
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sqlx::PgPool;
|
||||
use crate::auth::TOTPAuthenticatedUserInfo;
|
||||
use crate::org::user_has_org_assoc;
|
||||
use crate::role::{get_role, get_role_ids_for_ca, Role};
|
||||
|
||||
#[options("/v1/org/<_org>/roles")]
|
||||
pub fn options(_org: i32) -> &'static str { "" }
|
||||
#[options("/v1/org/<_org>/role")]
|
||||
pub fn options_roleadd(_org: i32) -> &'static str { "" }
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct RolesResponse {
|
||||
pub data: Vec<Role>
|
||||
}
|
||||
|
||||
#[get("/v1/org/<org>/roles")]
|
||||
pub async fn get_roles(org: i32, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<RolesResponse>), (Status, String)> {
|
||||
if !user_has_org_assoc(user.user_id, org, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? {
|
||||
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org")));
|
||||
}
|
||||
|
||||
let roles = match get_role_ids_for_ca(org, db.inner()).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", e)))
|
||||
}
|
||||
};
|
||||
|
||||
let mut resp = RolesResponse {
|
||||
data: vec![]
|
||||
};
|
||||
|
||||
for role in roles {
|
||||
let role_info = match get_role(role, db.inner()).await {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", e)))
|
||||
}
|
||||
};
|
||||
if let Some(info) = role_info {
|
||||
resp.data.push(info);
|
||||
} else {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "there was an error querying the db, please try again later", "missing role as returned by server - possibly deleted inbetween?")))
|
||||
}
|
||||
}
|
||||
|
||||
Ok((ContentType::JSON, Json(resp)))
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct RoleaddReq {
|
||||
pub name: String,
|
||||
pub description: String
|
||||
}
|
||||
|
||||
#[post("/v1/org/<org>/role", data = "<req>")]
|
||||
pub async fn role_add(req: Json<RoleaddReq>, org: i32, user: TOTPAuthenticatedUserInfo, db: &State<PgPool>) -> Result<(ContentType, String), (Status, String)> {
|
||||
if !user_has_org_assoc(user.user_id, org, db.inner()).await.map_err(|e| (Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))? {
|
||||
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_MISSING_ORG_AUTHORIZATION", "this user does not have permission to access this org")));
|
||||
}
|
||||
|
||||
match sqlx::query!("INSERT INTO roles (org, name, description) VALUES ($1, $2, $3)", org, req.name, req.description).execute(db.inner()).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
error!("[tfapi] dberror: {}", e);
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_INTERNAL_SERVER_ERR", "database returned error while trying to create role", "unable to insert role")))
|
||||
}
|
||||
}
|
||||
|
||||
Ok((ContentType::JSON, "{}".to_string()))
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use rocket::{post, State};
|
||||
use rocket::serde::json::Json;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use rocket::http::{ContentType, Status};
|
||||
use sqlx::PgPool;
|
||||
use crate::config::TFConfig;
|
||||
use crate::tokens::send_magic_link;
|
||||
use rocket::options;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct SignupRequest {
|
||||
pub email: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct SignupResponseMetadata {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct SignupResponse {
|
||||
pub data: Option<String>,
|
||||
pub metadata: SignupResponseMetadata,
|
||||
}
|
||||
/*
|
||||
created_on TIMESTAMP NOT NULL,
|
||||
|
||||
banned INTEGER NOT NULL,
|
||||
ban_reason VARCHAR(1024) NOT NULL
|
||||
*/
|
||||
#[options("/v1/signup")]
|
||||
pub async fn options() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
#[post("/v1/signup", data = "<req>")]
|
||||
pub async fn signup_request(req: Json<SignupRequest>, pool: &State<PgPool>, config: &State<TFConfig>) -> Result<(ContentType, Json<SignupResponse>), (Status, String)> {
|
||||
// figure out if the user already exists
|
||||
let mut id = -1;
|
||||
match sqlx::query!("SELECT id FROM users WHERE email = $1", req.email.clone()).fetch_optional(pool.inner()).await {
|
||||
Ok(res) => if let Some(r) = res { id = r.id as i64 },
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))
|
||||
}
|
||||
}
|
||||
|
||||
if id == -1 {
|
||||
let id_res = match sqlx::query!("INSERT INTO users (email, created_on, banned, ban_reason, totp_secret, totp_otpurl, totp_verified) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT DO NOTHING RETURNING id;", req.email, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64, 0, "", "", "", 0).fetch_one(pool.inner()).await {
|
||||
Ok(row) => row.id,
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))
|
||||
}
|
||||
};
|
||||
id = id_res as i64;
|
||||
}
|
||||
|
||||
// send magic link to email
|
||||
match send_magic_link(id, req.email.clone(), pool.inner(), config.inner()).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))
|
||||
}
|
||||
};
|
||||
|
||||
// this endpoint doesn't actually ever return an error? it will send you the magic link no matter what
|
||||
// this appears to do the exact same thing as /v1/auth/magic-link, but it doesn't check if you have an account (magic-link does)
|
||||
Ok((ContentType::JSON, Json(SignupResponse {
|
||||
data: None,
|
||||
metadata: SignupResponseMetadata {},
|
||||
})))
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use rocket::http::{ContentType, Status};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::{State, post};
|
||||
use sqlx::PgPool;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use crate::auth::PartialUserInfo;
|
||||
use crate::config::TFConfig;
|
||||
use crate::tokens::{create_totp_token, user_has_totp};
|
||||
use rocket::options;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct TotpAuthenticatorsRequest {}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct TotpAuthenticatorsResponseMetadata {}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct TotpAuthenticatorsResponseData {
|
||||
#[serde(rename = "totpToken")]
|
||||
pub totp_token: String,
|
||||
pub secret: String,
|
||||
pub url: String,
|
||||
}
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct TotpAuthenticatorsResponse {
|
||||
pub data: TotpAuthenticatorsResponseData,
|
||||
pub metadata: TotpAuthenticatorsResponseMetadata,
|
||||
}
|
||||
|
||||
#[options("/v1/totp-authenticators")]
|
||||
pub async fn options() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
|
||||
#[post("/v1/totp-authenticators", data = "<_req>")]
|
||||
pub async fn totp_authenticators_request(_req: Json<TotpAuthenticatorsRequest>, user: PartialUserInfo, db: &State<PgPool>, config: &State<TFConfig>) -> Result<(ContentType, Json<TotpAuthenticatorsResponse>), (Status, String)> {
|
||||
if match user_has_totp(user.user_id, db.inner()).await {
|
||||
Ok(b) => b,
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e)))
|
||||
} {
|
||||
return Err((Status::BadRequest, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\"}}]}}", "ERR_TOTP_ALREADY_EXISTS", "this user already has a totp authenticator on their account")))
|
||||
}
|
||||
|
||||
// generate a totp token
|
||||
let (totptoken, totpmachine) = match create_totp_token(user.email, db.inner(), config.inner()).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured issuing a totp token, try again later", e)))
|
||||
};
|
||||
|
||||
Ok((ContentType::JSON, Json(TotpAuthenticatorsResponse {
|
||||
data: TotpAuthenticatorsResponseData {
|
||||
totp_token: totptoken,
|
||||
secret: totpmachine.get_secret_base32(),
|
||||
url: totpmachine.get_url(),
|
||||
},
|
||||
metadata: TotpAuthenticatorsResponseMetadata {},
|
||||
})))
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rocket::{get, options};
|
||||
use rocket::http::{ContentType, Status};
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use sqlx::PgPool;
|
||||
|
||||
#[options("/v1/user/<_id>")]
|
||||
pub fn options(_id: i32) -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(crate = "rocket::serde")]
|
||||
pub struct UserInfoResponse {
|
||||
email: String
|
||||
}
|
||||
|
||||
#[get("/v1/user/<id>")]
|
||||
pub async fn get_user(id: i32, db: &State<PgPool>) -> Result<(ContentType, Json<UserInfoResponse>), (Status, String)> {
|
||||
let user = match sqlx::query!("SELECT email FROM users WHERE id = $1", id.clone() as i32).fetch_one(db.inner()).await {
|
||||
Ok(u) => u,
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_QL_QUERY_FAILED", "an error occurred while running the graphql query", e)))
|
||||
};
|
||||
|
||||
Ok((ContentType::JSON, Json(
|
||||
UserInfoResponse {
|
||||
email: user.email,
|
||||
}
|
||||
)))
|
||||
}
|
|
@ -1,75 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use rocket::http::{ContentType, Status};
|
||||
use crate::auth::PartialUserInfo;
|
||||
use rocket::post;
|
||||
use rocket::serde::json::Json;
|
||||
use rocket::State;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use sqlx::PgPool;
|
||||
use crate::tokens::{generate_auth_token, use_totp_token, verify_totp_token};
|
||||
use rocket::options;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyTotpAuthenticatorRequest {
|
||||
#[serde(rename = "totpToken")]
|
||||
pub totp_token: String,
|
||||
pub code: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyTotpAuthenticatorResponseMetadata {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyTotpAuthenticatorResponseData {
|
||||
#[serde(rename = "authToken")]
|
||||
pub auth_token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct VerifyTotpAuthenticatorResponse {
|
||||
pub data: VerifyTotpAuthenticatorResponseData,
|
||||
pub metadata: VerifyTotpAuthenticatorResponseMetadata,
|
||||
}
|
||||
|
||||
#[options("/v1/verify-totp-authenticator")]
|
||||
pub async fn options() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
|
||||
#[post("/v1/verify-totp-authenticator", data = "<req>")]
|
||||
pub async fn verify_totp_authenticator_request(req: Json<VerifyTotpAuthenticatorRequest>, db: &State<PgPool>, user: PartialUserInfo) -> Result<(ContentType, Json<VerifyTotpAuthenticatorResponse>), (Status, String)> {
|
||||
let totpmachine = match verify_totp_token(req.0.totp_token.clone(), user.email.clone(), db.inner()).await {
|
||||
Ok(t) => t,
|
||||
Err(e) => return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNAUTHORIZED", "this token is invalid", e)))
|
||||
};
|
||||
if !totpmachine.check_current(&req.0.code).unwrap() {
|
||||
return Err((Status::Unauthorized, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{}\",\"path\":\"totpToken\"}}]}}", "ERR_INVALID_TOTP_CODE", "Invalid TOTP code")))
|
||||
}
|
||||
match use_totp_token(req.0.totp_token, user.email, db.inner()).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e)))
|
||||
}
|
||||
Ok((ContentType::JSON, Json(VerifyTotpAuthenticatorResponse {
|
||||
data: VerifyTotpAuthenticatorResponseData { auth_token: match generate_auth_token(user.user_id as i64, user.session_id, db.inner()).await {
|
||||
Ok(at) => at,
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_UNABLE_TO_ISSUE", "an error occured trying to issue a session token, please try again later", e)))
|
||||
} },
|
||||
metadata: VerifyTotpAuthenticatorResponseMetadata {},
|
||||
})))
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
pub mod whoami;
|
|
@ -1,85 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use chrono::{NaiveDateTime, Utc};
|
||||
use serde::{Serialize, Deserialize};
|
||||
use rocket::{options, get, State};
|
||||
use rocket::http::{ContentType, Status};
|
||||
use rocket::serde::json::Json;
|
||||
use sqlx::PgPool;
|
||||
use crate::auth::PartialUserInfo;
|
||||
use crate::org::get_org_by_owner_id;
|
||||
use crate::tokens::user_has_totp;
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct WhoamiMetadata {}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct WhoamiActor {
|
||||
pub id: String,
|
||||
#[serde(rename = "organizationID")]
|
||||
pub organization_id: String,
|
||||
pub email: String,
|
||||
#[serde(rename = "createdAt")]
|
||||
pub created_at: String,
|
||||
#[serde(rename = "hasTOTPAuthenticator")]
|
||||
pub has_totpauthenticator: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct WhoamiData {
|
||||
#[serde(rename = "actorType")]
|
||||
pub actor_type: String,
|
||||
pub actor: WhoamiActor,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct WhoamiResponse {
|
||||
pub data: WhoamiData,
|
||||
pub metadata: WhoamiMetadata,
|
||||
}
|
||||
|
||||
#[options("/v2/whoami")]
|
||||
pub fn options() -> &'static str {
|
||||
""
|
||||
}
|
||||
|
||||
#[get("/v2/whoami")]
|
||||
pub async fn whoami_request(user: PartialUserInfo, db: &State<PgPool>) -> Result<(ContentType, Json<WhoamiResponse>), (Status, String)> {
|
||||
let org = match get_org_by_owner_id(user.user_id, db.inner()).await {
|
||||
Ok(b) => match b {
|
||||
Some(r) => r.to_string(),
|
||||
None => String::new()
|
||||
},
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_DBERROR", "an error occured trying to verify your user", e)))
|
||||
};
|
||||
Ok((ContentType::JSON, Json(WhoamiResponse {
|
||||
data: WhoamiData {
|
||||
actor_type: "user".to_string(),
|
||||
actor: WhoamiActor {
|
||||
id: user.user_id.to_string(),
|
||||
organization_id: org,
|
||||
email: user.email,
|
||||
created_at: NaiveDateTime::from_timestamp_opt(user.created_at, 0).unwrap().and_local_timezone(Utc).unwrap().to_rfc3339(),
|
||||
has_totpauthenticator: match user_has_totp(user.user_id, db.inner()).await {
|
||||
Ok(b) => b,
|
||||
Err(e) => return Err((Status::InternalServerError, format!("{{\"errors\":[{{\"code\":\"{}\",\"message\":\"{} - {}\"}}]}}", "ERR_DBERROR", "an error occured trying to verify your user", e)))
|
||||
},
|
||||
}
|
||||
},
|
||||
metadata: WhoamiMetadata {},
|
||||
})))
|
||||
}
|
|
@ -1,109 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use std::error::Error;
|
||||
use log::info;
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
use crate::config::TFConfig;
|
||||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
use totp_rs::{Secret, TOTP};
|
||||
use crate::util::{TOTP_ALGORITHM, TOTP_DIGITS, TOTP_ISSUER, TOTP_SKEW, TOTP_STEP};
|
||||
|
||||
// https://admin.defined.net/auth/magic-link?email=coredoescode%40gmail.com&token=ml-ckBsgw_5IdK5VYgseBYcoV_v_cQjtdq1re_RhDu_MKg
|
||||
pub async fn send_magic_link(id: i64, email: String, db: &PgPool, config: &TFConfig) -> Result<(), Box<dyn Error>> {
|
||||
let otp = format!("ml-{}", Uuid::new_v4());
|
||||
let otp_url = config.web_root.join(&format!("/auth/magic-link?email={}&token={}", urlencoding::encode(&email.clone()), otp.clone())).unwrap();
|
||||
sqlx::query!("INSERT INTO magic_links (id, user_id, expires_on) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;", otp, id as i32, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32 + config.magic_links_valid_for as i32).execute(db).await?;
|
||||
// TODO: send email
|
||||
info!("sent magic link {} to {}, valid for {} seconds", otp_url, email.clone(), config.magic_links_valid_for);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn generate_session_token(user_id: i64, db: &PgPool, config: &TFConfig) -> Result<String, Box<dyn Error>> {
|
||||
let token = format!("st-{}", Uuid::new_v4());
|
||||
sqlx::query!("INSERT INTO session_tokens (id, user_id, expires_on) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;", token, user_id as i32, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32 + config.session_tokens_valid_for as i32).execute(db).await?;
|
||||
Ok(token)
|
||||
}
|
||||
pub async fn validate_session_token(token: String, db: &PgPool) -> Result<i64, Box<dyn Error>> {
|
||||
Ok(sqlx::query!("SELECT user_id FROM session_tokens WHERE id = $1 AND expires_on > $2", token, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32).fetch_one(db).await?.user_id as i64)
|
||||
}
|
||||
|
||||
pub async fn generate_auth_token(user_id: i64, session_id: String, db: &PgPool) -> Result<String, Box<dyn Error>> {
|
||||
let token = format!("at-{}", Uuid::new_v4());
|
||||
sqlx::query!("INSERT INTO auth_tokens (id, session_token, user_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING;", token, session_id, user_id as i32).execute(db).await?;
|
||||
Ok(token)
|
||||
}
|
||||
pub async fn validate_auth_token(token: String, session_id: String, db: &PgPool) -> Result<(), Box<dyn Error>> {
|
||||
validate_session_token(session_id.clone(), db).await?;
|
||||
sqlx::query!("SELECT * FROM auth_tokens WHERE id = $1 AND session_token = $2", token, session_id).fetch_one(db).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
CREATE TABLE totp_create_tokens (
|
||||
id VARCHAR(39) NOT NULL PRIMARY KEY,
|
||||
expires_on INTEGER NOT NULL,
|
||||
totp_otpurl VARCHAR(3000) NOT NULL,
|
||||
totp_secret VARCHAR(128) NOT NULL
|
||||
);
|
||||
*/
|
||||
|
||||
pub async fn create_totp_token(email: String, db: &PgPool, config: &TFConfig) -> Result<(String, TOTP), Box<dyn Error>> {
|
||||
// create the TOTP parameters
|
||||
|
||||
let secret = Secret::generate_secret();
|
||||
let totpmachine = TOTP::new(TOTP_ALGORITHM, TOTP_DIGITS, TOTP_SKEW, TOTP_STEP, secret.to_bytes().unwrap(), Some(TOTP_ISSUER.to_string()), email).unwrap();
|
||||
let otpurl = totpmachine.get_url();
|
||||
let otpsecret = totpmachine.get_secret_base32();
|
||||
|
||||
let otpid = format!("totp-{}", Uuid::new_v4());
|
||||
|
||||
sqlx::query!("INSERT INTO totp_create_tokens (id, expires_on, totp_otpurl, totp_secret) VALUES ($1, $2, $3, $4);", otpid.clone(), (SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() + config.totp_verification_valid_for) as i32, otpurl, otpsecret).execute(db).await?;
|
||||
|
||||
Ok((otpid, totpmachine))
|
||||
}
|
||||
|
||||
pub async fn verify_totp_token(otpid: String, email: String, db: &PgPool) -> Result<TOTP, Box<dyn Error>> {
|
||||
let totprow = sqlx::query!("SELECT * FROM totp_create_tokens WHERE id = $1 AND expires_on > $2", otpid, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i32).fetch_one(db).await?;
|
||||
let secret = Secret::Encoded(totprow.totp_secret);
|
||||
let totpmachine = TOTP::new(TOTP_ALGORITHM, TOTP_DIGITS, TOTP_SKEW, TOTP_STEP, secret.to_bytes().unwrap(), Some(TOTP_ISSUER.to_string()), email).unwrap();
|
||||
|
||||
if totpmachine.get_url() != totprow.totp_otpurl {
|
||||
return Err("OTPURLs do not match (email does not match?)".into())
|
||||
}
|
||||
|
||||
Ok(totpmachine)
|
||||
}
|
||||
|
||||
pub async fn use_totp_token(otpid: String, email: String, db: &PgPool) -> Result<TOTP, Box<dyn Error>> {
|
||||
let totpmachine = verify_totp_token(otpid.clone(), email.clone(), db).await?;
|
||||
sqlx::query!("DELETE FROM totp_create_tokens WHERE id = $1", otpid).execute(db).await?;
|
||||
sqlx::query!("UPDATE users SET totp_otpurl = $1, totp_secret = $2, totp_verified = 1 WHERE email = $3", totpmachine.get_url(), totpmachine.get_secret_base32(), email).execute(db).await?;
|
||||
Ok(totpmachine)
|
||||
}
|
||||
|
||||
pub async fn get_totpmachine(user: i32, db: &PgPool) -> Result<TOTP, Box<dyn Error>> {
|
||||
let user = sqlx::query!("SELECT totp_secret, totp_otpurl, email FROM users WHERE id = $1", user).fetch_one(db).await?;
|
||||
let secret = Secret::Encoded(user.totp_secret);
|
||||
Ok(TOTP::new(TOTP_ALGORITHM, TOTP_DIGITS, TOTP_SKEW, TOTP_STEP, secret.to_bytes().unwrap(), Some(TOTP_ISSUER.to_string()), user.email).unwrap())
|
||||
}
|
||||
|
||||
pub async fn user_has_totp(user: i32, db: &PgPool) -> Result<bool, Box<dyn Error>> {
|
||||
Ok(sqlx::query!("SELECT totp_verified FROM users WHERE id = $1", user).fetch_one(db).await?.totp_verified == 1)
|
||||
}
|
|
@ -1,31 +0,0 @@
|
|||
// trifid-api, an open source reimplementation of the Defined Networking nebula management server.
|
||||
// Copyright (C) 2023 c0repwn3r
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
use base64::Engine;
|
||||
use totp_rs::Algorithm;
|
||||
|
||||
pub const TOTP_ALGORITHM: Algorithm = Algorithm::SHA1;
|
||||
pub const TOTP_DIGITS: usize = 6;
|
||||
pub const TOTP_SKEW: u8 = 1;
|
||||
pub const TOTP_STEP: u64 = 30;
|
||||
pub const TOTP_ISSUER: &str = "trifidapi";
|
||||
|
||||
pub fn base64decode(val: &str) -> Result<Vec<u8>, base64::DecodeError> {
|
||||
base64::engine::general_purpose::STANDARD.decode(val)
|
||||
}
|
||||
pub fn base64encode(val: Vec<u8>) -> String {
|
||||
base64::engine::general_purpose::STANDARD.encode(val)
|
||||
}
|
Loading…
Reference in New Issue