diff --git a/src/api/auth.rs b/src/api/auth.rs index e6cab0a..0cd0626 100644 --- a/src/api/auth.rs +++ b/src/api/auth.rs @@ -59,9 +59,8 @@ pub struct AuthResponse { issued_at: String, } -/// In the returned UserToken::user, only the username is specified -fn create_jwt_token(account: Option<&str>, scopes: Vec) -> anyhow::Result { - let key: Hmac = Hmac::new_from_slice(b"some-secret")?; +fn create_jwt_token(jwt_key: String, account: Option<&str>, scopes: Vec) -> anyhow::Result { + let key: Hmac = Hmac::new_from_slice(jwt_key.as_bytes())?; let now = chrono::offset::Utc::now(); @@ -186,11 +185,12 @@ pub async fn auth_basic_get( scope.actions.retain(|a| *a == Action::Pull); } - let token = create_jwt_token(None, auth.scope).map_err(|_| { - error!("Failed to create jwt token!"); + let token = create_jwt_token(state.config.jwt_key.clone(), None, auth.scope) + .map_err(|_| { + error!("Failed to create jwt token!"); - StatusCode::INTERNAL_SERVER_ERROR - })?; + StatusCode::INTERNAL_SERVER_ERROR + })?; let token_str = token.token; let now_format = format!("{}", token.created_at.format("%+")); @@ -290,11 +290,12 @@ pub async fn auth_basic_get( debug!("User password is correct"); let now = SystemTime::now(); - let token = create_jwt_token(Some(account), vec![]).map_err(|_| { - error!("Failed to create jwt token!"); + let token = create_jwt_token(state.config.jwt_key.clone(), Some(account), vec![]) + .map_err(|_| { + error!("Failed to create jwt token!"); - StatusCode::INTERNAL_SERVER_ERROR - })?; + StatusCode::INTERNAL_SERVER_ERROR + })?; let token_str = token.token; debug!("Created jwt token"); diff --git a/src/config.rs b/src/config.rs index 37100d8..55a009d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -71,6 +71,8 @@ pub struct Config { pub database: DatabaseConfig, pub storage: StorageConfig, pub tls: Option, + #[serde(skip)] + pub jwt_key: String, } #[allow(dead_code)] diff --git a/src/database/mod.rs b/src/database/mod.rs index 76de956..1cf3c4d 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,5 +1,8 @@ use async_trait::async_trait; -use sqlx::{Sqlite, Pool}; +use hmac::{Hmac, digest::KeyInit}; +use rand::{Rng, distributions::Alphanumeric}; +use sha2::Sha256; +use sqlx::{Sqlite, Pool, sqlite::SqliteError}; use tracing::{debug, warn}; use chrono::{DateTime, Utc, NaiveDateTime, TimeZone}; @@ -8,12 +11,13 @@ use crate::dto::{Tag, user::{User, RepositoryPermissions, RegistryUserType, Perm #[async_trait] pub trait Database { - // Digest related functions /// Create the tables in the database async fn create_schema(&self) -> anyhow::Result<()>; + async fn get_jwt_secret(&self) -> anyhow::Result; + // Tag related functions /// Get tags associated with a repository @@ -67,14 +71,56 @@ pub trait Database { #[async_trait] impl Database for Pool { async fn create_schema(&self) -> anyhow::Result<()> { - sqlx::query(include_str!("schemas/schema.sql")) - .execute(self).await?; + let orca_version = "0.1.0"; + let schema_version = "0.0.1"; - debug!("Created database schema"); + let row: Option<(u32, )> = match sqlx::query_as("SELECT COUNT(1) FROM orca WHERE \"schema_version\" = ?") + .bind(schema_version) + .fetch_one(self).await { + Ok(row) => Some(row), + Err(e) => match e { + sqlx::Error::RowNotFound => { + None + }, + // ignore no such table errors + sqlx::Error::Database(b) if b.message().starts_with("no such table") => None, + _ => { + return Err(anyhow::Error::new(e)); + } + } + }; + + if row.is_none() || row.unwrap().0 == 0 { + let jwt_sec: String = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(16) + .map(char::from) + .collect(); + + // create schema + // TODO: Check if needed + sqlx::query(include_str!("schemas/schema.sql")) + .execute(self).await?; + debug!("Created database schema"); + + sqlx::query("INSERT INTO orca(orca_version, schema_version, jwt_secret) VALUES (?, ?, ?)") + .bind(orca_version) + .bind(schema_version) + .bind(jwt_sec) + .execute(self).await?; + debug!("Inserted information about orca!"); + } Ok(()) } + async fn get_jwt_secret(&self) -> anyhow::Result { + let rows: (String, ) = sqlx::query_as("SELECT jwt_secret FROM orca WHERE id = (SELECT max(id) FROM orca)") + .fetch_one(self).await?; + + Ok(rows.0) + } + async fn link_manifest_layer(&self, manifest_digest: &str, layer_digest: &str) -> anyhow::Result<()> { sqlx::query("INSERT INTO manifest_layers(manifest, layer_digest) VALUES (?, ?)") .bind(manifest_digest) diff --git a/src/database/schemas/schema.sql b/src/database/schemas/schema.sql index 9031dc5..a81cbcc 100644 --- a/src/database/schemas/schema.sql +++ b/src/database/schemas/schema.sql @@ -1,3 +1,10 @@ +CREATE TABLE IF NOT EXISTS orca ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + orca_version TEXT NOT NULL, + schema_version TEXT NOT NULL, + jwt_secret TEXT NOT NULL +); + CREATE TABLE IF NOT EXISTS projects ( name TEXT NOT NULL UNIQUE PRIMARY KEY, -- 0 = private, 1 = public diff --git a/src/main.rs b/src/main.rs index 0b59da8..caaa047 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,7 +69,7 @@ async fn change_request_paths(mut request: Request, next: Next) -> Resu #[tokio::main] async fn main() -> anyhow::Result<()> { - let config = Config::new() + let mut config = Config::new() .expect("Failure to parse config!"); tracing_subscriber::fmt() @@ -92,6 +92,9 @@ async fn main() -> anyhow::Result<()> { .connect_with(connection_options).await?; pool.create_schema().await?; + // set jwt key + config.jwt_key = pool.get_jwt_secret().await?; + let storage_driver: Mutex> = match &config.storage { StorageConfig::Filesystem(fs) => { Mutex::new(Box::new(FilesystemDriver::new(&fs.path)))