Skip to content

Commit

Permalink
Merge pull request #114 from nightly-labs/clean-api
Browse files Browse the repository at this point in the history
Clean api
  • Loading branch information
Giems authored Mar 6, 2024
2 parents d130ce4 + ec65114 commit d45c8df
Show file tree
Hide file tree
Showing 30 changed files with 342 additions and 345 deletions.
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
ENV=DEV # PROD or DEV
ONLY_RELAY_SERVICE=True # TRUE - This will start only nightly relay service without cloud service
NONCE=VERY_SECRET_NONCE
JWT_SECRET=VERY_SECRET_SECRET
2 changes: 2 additions & 0 deletions database/migrations/0002_team.sql
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
CREATE TABLE team(
team_id TEXT NOT NULL UNIQUE,
team_name TEXT NOT NULL,
personal BOOLEAN NOT NULL,
subscription subscription,
team_admin_id TEXT NOT NULL,
registration_timestamp TIMESTAMPTZ NOT NULL
Expand Down
4 changes: 2 additions & 2 deletions database/migrations/0004_registered_apps.sql
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
CREATE TABLE registered_apps(
team_id TEXT NOT NULL REFERENCES team(team_id) ON DELETE CASCADE,
team_id TEXT NOT NULL REFERENCES team(team_id),
app_id TEXT NOT NULL UNIQUE,
app_name TEXT NOT NULL UNIQUE,
app_name TEXT NOT NULL,
whitelisted_domains TEXT [] NOT NULL,
ack_public_keys TEXT [] NOT NULL,
email TEXT,
Expand Down
4 changes: 2 additions & 2 deletions database/src/tables/registered_app/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,13 @@ impl Db {
pub async fn get_registered_app_by_app_name(
&self,
app_name: &String,
) -> Result<DbRegisteredApp, sqlx::Error> {
) -> Result<Option<DbRegisteredApp>, sqlx::Error> {
let query = format!("SELECT * FROM {REGISTERED_APPS_TABLE_NAME} WHERE app_name = $1");
let typed_query = query_as::<_, DbRegisteredApp>(&query);

return typed_query
.bind(&app_name)
.fetch_one(&self.connection_pool)
.fetch_optional(&self.connection_pool)
.await;
}
}
7 changes: 6 additions & 1 deletion database/src/tables/team/table_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ use sqlx::{
};

pub const TEAM_TABLE_NAME: &str = "team";
pub const TEAM_KEYS: &str = "team_id, subscription, team_admin_id, registration_timestamp";
pub const TEAM_KEYS: &str =
"team_id, team_name, personal, subscription, team_admin_id, registration_timestamp";

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Team {
pub team_id: String,
pub personal: bool,
pub team_name: String,
// Subscription is required to get access to the statistics
pub subscription: Option<Subscription>,
pub team_admin_id: String,
Expand All @@ -21,6 +24,8 @@ impl FromRow<'_, PgRow> for Team {
fn from_row(row: &sqlx::postgres::PgRow) -> std::result::Result<Self, sqlx::Error> {
Ok(Team {
team_id: row.get("team_id"),
team_name: row.get("team_name"),
personal: row.get("personal"),
subscription: row.get("subscription"),
registration_timestamp: row.get("registration_timestamp"),
team_admin_id: row.get("team_admin_id"),
Expand Down
6 changes: 5 additions & 1 deletion database/src/tables/team/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ impl Db {
team: &Team,
) -> Result<(), sqlx::Error> {
let query_body =
format!("INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4)");
format!("INSERT INTO {TEAM_TABLE_NAME} ({TEAM_KEYS}) VALUES ($1, $2, $3, $4, $5, $6)");

let query_result = query(&query_body)
.bind(&team.team_id)
.bind(&team.team_name)
.bind(&team.personal)
.bind(&team.subscription)
.bind(&team.team_admin_id)
.bind(&team.registration_timestamp)
Expand Down Expand Up @@ -121,6 +123,8 @@ mod tests {
// Create team and register app
let team = Team {
team_id: "test_team_id".to_string(),
team_name: "test_team_name".to_string(),
personal: false,
subscription: None,
team_admin_id: "test_team_admin_id".to_string(),
registration_timestamp: to_microsecond_precision(&Utc::now()),
Expand Down
2 changes: 2 additions & 0 deletions database/src/tables/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ pub mod test_utils {

let team = Team {
team_id: team_id.clone(),
team_name: "test_team_name".to_string(),
personal: false,
subscription: None,
team_admin_id: admin.user_id.clone(),
registration_timestamp: registration_timestamp,
Expand Down
2 changes: 1 addition & 1 deletion server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,4 @@ once_cell = { workspace = true }
chrono = { workspace = true }
jsonwebtoken = { workspace = true }
pwhash = { workspace = true }
rand = { workspace = true }
rand = { workspace = true }
37 changes: 1 addition & 36 deletions server/src/auth/auth_token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,42 +38,7 @@ impl AuthToken {
exp: (Utc::now() + Duration::minutes(60 * 7 * 24)).timestamp() as u64, // Token expires in 7 days
}
}
pub fn new_read_only(user_id: &String, ip: Option<SocketAddr>) -> Self {
AuthToken {
id: uuid7::uuid7().to_string(),
user_id: user_id.clone(),
ip: match ip {
Some(ip) => Some(ip.ip()),
None => None,
},
token_type: AuthTokenType::ReadOnly,
exp: (Utc::now() + Duration::minutes(60 * 7 * 24)).timestamp() as u64, // Token expires in 7 days
}
}
pub fn new_api_read_only_refresh(user_id: &String, ip: Option<SocketAddr>) -> Self {
AuthToken {
id: uuid7::uuid7().to_string(),
user_id: user_id.clone(),
ip: match ip {
Some(ip) => Some(ip.ip()),
None => None,
},
token_type: AuthTokenType::ApiReadRefresh,
exp: u64::MAX, // Token never expires
}
}
pub fn new_api_read_write_refresh(user_id: &String, ip: Option<SocketAddr>) -> Self {
AuthToken {
id: uuid7::uuid7().to_string(),
user_id: user_id.clone(),
ip: match ip {
Some(ip) => Some(ip.ip()),
None => None,
},
token_type: AuthTokenType::ApiReadWriteRefresh,
exp: u64::MAX, // Token never expires
}
}

pub fn encode(&self, secret: &str) -> Result<String, jsonwebtoken::errors::Error> {
encode(
&Header::new(Algorithm::HS256),
Expand Down
7 changes: 2 additions & 5 deletions server/src/auth/auth_token_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ use strum::{Display, EnumString};

#[derive(Debug, Serialize, PartialEq, Eq, Deserialize, Clone, Display, EnumString)]
pub enum AuthTokenType {
Access, // Usually short-lived, used to access protected resources
Refresh, // Usually long-lived, used to obtain new access tokens
ReadOnly, // Usually short-lived, used to access protected resources
ApiReadWriteRefresh, // Allow to get ReadOnly token generated from app
ApiReadRefresh, // Allow to get Access token generated from app
Access, // Usually short-lived, used to access protected resources
Refresh, // Usually long-lived, used to obtain new access tokens
}
5 changes: 2 additions & 3 deletions server/src/bin/nightly-connect-server.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use server::env::ONLY_RELAY_SERVICE;
use server::routes::router::get_router;
use std::net::SocketAddr;
use std::sync::mpsc::channel;

#[tokio::main]
async fn main() {
dotenvy::dotenv().expect(".env file not found");

let router = get_router().await;
let router = get_router(ONLY_RELAY_SERVICE()).await;
let listener = tokio::net::TcpListener::bind(&"127.0.0.1:6969")
.await
.expect("Failed to bind socket");
Expand Down
16 changes: 15 additions & 1 deletion server/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng};
pub struct ENV {
pub ENVIRONMENT: String,
pub JWT_SECRET: String,
pub ONLY_RELAY_SERVICE: bool,
pub NONCE: String,
}
pub fn get_env() -> &'static ENV {
static INSTANCE: OnceCell<ENV> = OnceCell::new();

INSTANCE.get_or_init(|| {
dotenvy::from_filename(".env").ok();
let ENVIRONMENT = std::env::var("ENVIRONMENT").expect("Failed to get ENVIRONMENT env");
let ENVIRONMENT = std::env::var("ENV").expect("Failed to get ENV env");
let ENVIRONMENT = ENVIRONMENT.as_str();

let env = ENV {
Expand All @@ -31,6 +33,10 @@ pub fn get_env() -> &'static ENV {
}
_ => panic!("Invalid ENVIRONMENT"),
},
ONLY_RELAY_SERVICE: std::env::var("ONLY_RELAY_SERVICE")
.expect("Failed to get ONLY_RELAY_SERVICE env")
.eq_ignore_ascii_case("true"),
NONCE: std::env::var("NONCE").expect("Failed to get NONCE env"),
};
return env;
})
Expand All @@ -42,3 +48,11 @@ pub fn ENVIRONMENT() -> &'static str {
pub fn JWT_SECRET() -> &'static str {
get_env().JWT_SECRET.as_str()
}

pub fn ONLY_RELAY_SERVICE() -> bool {
get_env().ONLY_RELAY_SERVICE
}

pub fn NONCE() -> &'static str {
get_env().NONCE.as_str()
}
31 changes: 31 additions & 0 deletions server/src/http/cloud/cloud_middleware.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use crate::state::ServerState;
use axum::{extract::Request, http::StatusCode, middleware::Next, response::IntoResponse};
use std::sync::Arc;

pub async fn db_cloud_middleware(
req: Request,
next: Next,
) -> Result<impl IntoResponse, (StatusCode, String)> {
// Extract the state from the request extensions
let state = match req.extensions().get::<Arc<ServerState>>() {
Some(state) => state,
None => {
return Err((
StatusCode::INTERNAL_SERVER_ERROR,
"Corrupted server state".to_string(),
))
}
};

// Check if the database is connected
if state.db.is_some() {
// If the database is connected, pass the request to the next middleware or handler
Ok(next.run(req).await)
} else {
// If the database is not connected, return an error response
Err((
StatusCode::FORBIDDEN,
"Cloud endpoints are disabled".to_string(),
))
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use crate::{auth::AuthToken, env::JWT_SECRET};
use crate::{
auth::AuthToken,
env::{JWT_SECRET, NONCE},
};
use axum::{
extract::{ConnectInfo, State},
http::StatusCode,
Expand Down Expand Up @@ -29,17 +32,27 @@ pub struct HttpLoginResponse {

pub async fn login_with_password(
ConnectInfo(ip): ConnectInfo<SocketAddr>,
State(db): State<Arc<Db>>,
State(db): State<Option<Arc<Db>>>,
Json(request): Json<HttpLoginRequest>,
) -> Result<Json<HttpLoginResponse>, (StatusCode, String)> {
// Db connection has already been checked in the middleware
let db = db.as_ref().ok_or((
StatusCode::INTERNAL_SERVER_ERROR,
"Failed to get database connection".to_string(),
))?;

// Check if user exists
let user = db
.get_user_by_email(&request.email)
.await
.map_err(|_| (StatusCode::BAD_REQUEST, "Could not find user".to_string()))?;

// Verify password
if bcrypt::verify(request.password, &user.password_hash) == false {
if bcrypt::verify(
format!("{}_{}", NONCE(), request.password),
&user.password_hash,
) == false
{
return Err((StatusCode::BAD_REQUEST, "Incorrect Password".to_string()));
}

Expand Down
4 changes: 4 additions & 0 deletions server/src/http/cloud/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
pub mod cloud_middleware;
pub mod login_with_password;
pub mod register_new_app;
pub mod register_with_password;
Loading

0 comments on commit d45c8df

Please sign in to comment.