diff --git a/ee/tabby-webserver/src/schema/auth.rs b/ee/tabby-webserver/src/schema/auth.rs index 91ff0f3..e4ca305 100644 --- a/ee/tabby-webserver/src/schema/auth.rs +++ b/ee/tabby-webserver/src/schema/auth.rs @@ -8,17 +8,19 @@ use juniper::{FieldError, GraphQLObject, IntoFieldError, ScalarValue}; use lazy_static::lazy_static; use serde::{Deserialize, Serialize}; use thiserror::Error; +use tracing::{error, warn}; use uuid::Uuid; use validator::ValidationErrors; use super::from_validation_errors; lazy_static! { + static ref JWT_TOKEN_SECRET: String = jwt_token_secret(); static ref JWT_ENCODING_KEY: jwt::EncodingKey = jwt::EncodingKey::from_secret( - jwt_token_secret().as_bytes() + JWT_TOKEN_SECRET.as_bytes() ); static ref JWT_DECODING_KEY: jwt::DecodingKey = jwt::DecodingKey::from_secret( - jwt_token_secret().as_bytes() + JWT_TOKEN_SECRET.as_bytes() ); static ref JWT_DEFAULT_EXP: u64 = 30 * 60; // 30 minutes } @@ -36,7 +38,19 @@ pub fn validate_jwt(token: &str) -> jwt::errors::Result { } fn jwt_token_secret() -> String { - std::env::var("TABBY_WEBSERVER_JWT_TOKEN_SECRET").unwrap_or("default_secret".to_string()) + let jwt_secret = match std::env::var("TABBY_WEBSERVER_JWT_TOKEN_SECRET") { + Ok(x) => x, + Err(_) => { + warn!(r"TABBY_WEBSERVER_JWT_TOKEN_SECRET is not set. Tabby generates a one-time (non-persisted) JWT token solely for testing purposes."); + Uuid::new_v4().to_string() + } + }; + + if uuid::Uuid::parse_str(&jwt_secret).is_err() { + warn!("JWT token secret needs to be in standard uuid format to ensure its security, you might generate one at https://www.uuidgenerator.net"); + } + + jwt_secret } pub fn generate_refresh_token() -> String { diff --git a/ee/tabby-webserver/src/service/db/mod.rs b/ee/tabby-webserver/src/service/db/mod.rs index 1ac9453..8ff6575 100644 --- a/ee/tabby-webserver/src/service/db/mod.rs +++ b/ee/tabby-webserver/src/service/db/mod.rs @@ -36,7 +36,10 @@ lazy_static! { is_admin BOOLEAN NOT NULL DEFAULT 0, created_at TIMESTAMP DEFAULT (DATETIME('now')), updated_at TIMESTAMP DEFAULT (DATETIME('now')), - CONSTRAINT `idx_email` UNIQUE (`email`) + auth_token VARCHAR(128) NOT NULL, + + CONSTRAINT `idx_email` UNIQUE (`email`) + CONSTRAINT `idx_auth_token` UNIQUE (`auth_token`) ); "# ) diff --git a/ee/tabby-webserver/src/service/db/users.rs b/ee/tabby-webserver/src/service/db/users.rs index 7403afd..edfac09 100644 --- a/ee/tabby-webserver/src/service/db/users.rs +++ b/ee/tabby-webserver/src/service/db/users.rs @@ -3,6 +3,7 @@ use anyhow::Result; use chrono::{DateTime, Utc}; use rusqlite::{params, OptionalExtension, Row}; +use uuid::Uuid; use super::DbConn; @@ -15,11 +16,14 @@ pub struct User { pub email: String, pub password_encrypted: String, pub is_admin: bool, + + /// To authenticate IDE extensions / plugins to access code completion / chat api endpoints. + auth_token: String, } impl User { fn select(clause: &str) -> String { - r#"SELECT id, email, password_encrypted, is_admin, created_at, updated_at FROM users WHERE "# + r#"SELECT id, email, password_encrypted, is_admin, created_at, updated_at, auth_token FROM users WHERE "# .to_owned() + clause } @@ -32,6 +36,7 @@ impl User { is_admin: row.get(3)?, created_at: row.get(4)?, updated_at: row.get(5)?, + auth_token: row.get(6)?, }) } } @@ -47,9 +52,9 @@ impl DbConn { .conn .call(move |c| { let mut stmt = c.prepare( - r#"INSERT INTO users (email, password_encrypted, is_admin) VALUES (?, ?, ?)"#, + r#"INSERT INTO users (email, password_encrypted, is_admin, auth_token) VALUES (?, ?, ?, ?)"#, )?; - let id = stmt.insert((email, password_encrypted, is_admin))?; + let id = stmt.insert((email, password_encrypted, is_admin, Uuid::new_v4().to_string()))?; Ok(id) }) .await?;