diff --git a/libsql-ffi/build.rs b/libsql-ffi/build.rs index 7e2e263faa..26f5540421 100644 --- a/libsql-ffi/build.rs +++ b/libsql-ffi/build.rs @@ -264,6 +264,7 @@ fn copy_multiple_ciphers(out_dir: &str, out_path: &Path) { } fn build_multiple_ciphers(out_path: &Path) { + let target = env::var("TARGET").unwrap(); let bindgen_rs_path = if cfg!(feature = "session") { "bundled/bindings/session_bindgen.rs" } else { @@ -298,12 +299,8 @@ fn build_multiple_ciphers(out_path: &Path) { let mut cmake_opts: Vec<&str> = vec![]; - let cargo_build_target = env::var("CARGO_BUILD_TARGET").unwrap_or_default(); - let cross_cc_var_name = format!("CC_{}", cargo_build_target.replace("-", "_")); - let cross_cc = env::var(&cross_cc_var_name).ok(); - - let cross_cxx_var_name = format!("CXX_{}", cargo_build_target.replace("-", "_")); - let cross_cxx = env::var(&cross_cxx_var_name).ok(); + let cc = env("CC"); + let cxx = env("CXX"); let toolchain_path = sqlite3mc_build_dir.join("toolchain.cmake"); let cmake_toolchain_opt = format!("-DCMAKE_TOOLCHAIN_FILE=toolchain.cmake"); @@ -315,20 +312,22 @@ fn build_multiple_ciphers(out_path: &Path) { .open(toolchain_path.clone()) .unwrap(); - if let Some(ref cc) = cross_cc { + if let Some(ref cc) = cc { + let cc = cc.clone().into_string().unwrap(); if cc.contains("aarch64") && cc.contains("linux") { cmake_opts.push(&cmake_toolchain_opt); writeln!(toolchain_file, "set(CMAKE_SYSTEM_NAME \"Linux\")").unwrap(); writeln!(toolchain_file, "set(CMAKE_SYSTEM_PROCESSOR \"arm64\")").unwrap(); } } - if let Some(cc) = cross_cc { + if let Some(cc) = cc { + let cc = cc.into_string().unwrap(); writeln!(toolchain_file, "set(CMAKE_C_COMPILER {})", cc).unwrap(); } - if let Some(cxx) = cross_cxx { + if let Some(cxx) = cxx { + let cxx = cxx.into_string().unwrap(); writeln!(toolchain_file, "set(CMAKE_CXX_COMPILER {})", cxx).unwrap(); } - cmake_opts.push("-DCMAKE_BUILD_TYPE=Release"); cmake_opts.push("-DSQLITE3MC_STATIC=ON"); cmake_opts.push("-DCODEC_TYPE=AES256"); @@ -340,7 +339,7 @@ fn build_multiple_ciphers(out_path: &Path) { cmake_opts.push("-DSQLITE_USE_URI=ON"); cmake_opts.push("-DCMAKE_POSITION_INDEPENDENT_CODE=ON"); - if cargo_build_target.contains("musl") { + if target.contains("musl") { cmake_opts.push("-DCMAKE_C_FLAGS=\"-U_FORTIFY_SOURCE\""); cmake_opts.push("-DCMAKE_CXX_FLAGS=\"-U_FORTIFY_SOURCE\""); } diff --git a/libsql-replication/src/injector/mod.rs b/libsql-replication/src/injector/mod.rs index 0c44b5700b..80443964fe 100644 --- a/libsql-replication/src/injector/mod.rs +++ b/libsql-replication/src/injector/mod.rs @@ -1,6 +1,6 @@ -use std::collections::VecDeque; use std::path::Path; use std::sync::Arc; +use std::{collections::VecDeque, path::PathBuf}; use parking_lot::Mutex; use rusqlite::OpenFlags; @@ -33,6 +33,11 @@ pub struct Injector { // connection must be dropped before the hook context connection: Arc>>, biggest_uncommitted_seen: FrameNo, + + // Connection config items used to recreate the injection connection + path: PathBuf, + encryption_config: Option, + auto_checkpoint: u32, } /// Methods from this trait are called before and after performing a frame injection. @@ -46,17 +51,19 @@ impl Injector { auto_checkpoint: u32, encryption_config: Option, ) -> Result { + let path = path.as_ref().to_path_buf(); + let buffer = FrameBuffer::default(); let wal_manager = InjectorWalManager::new(buffer.clone()); let connection = libsql_sys::Connection::open( - path, + &path, OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_URI | OpenFlags::SQLITE_OPEN_NO_MUTEX, wal_manager, auto_checkpoint, - encryption_config, + encryption_config.clone(), )?; Ok(Self { @@ -65,6 +72,10 @@ impl Injector { capacity, connection: Arc::new(Mutex::new(connection)), biggest_uncommitted_seen: 0, + + path, + encryption_config, + auto_checkpoint, }) } @@ -161,7 +172,24 @@ impl Injector { } fn begin_txn(&mut self) -> Result<(), Error> { - let conn = self.connection.lock(); + let mut conn = self.connection.lock(); + + { + let wal_manager = InjectorWalManager::new(self.buffer.clone()); + let new_conn = libsql_sys::Connection::open( + &self.path, + OpenFlags::SQLITE_OPEN_READ_WRITE + | OpenFlags::SQLITE_OPEN_CREATE + | OpenFlags::SQLITE_OPEN_URI + | OpenFlags::SQLITE_OPEN_NO_MUTEX, + wal_manager, + self.auto_checkpoint, + self.encryption_config.clone(), + )?; + + let _ = std::mem::replace(&mut *conn, new_conn); + } + conn.pragma_update(None, "writable_schema", "true")?; let mut stmt = conn.prepare_cached("BEGIN IMMEDIATE")?; diff --git a/libsql-server/output.sql b/libsql-server/output.sql new file mode 100644 index 0000000000..f7a7a210ff --- /dev/null +++ b/libsql-server/output.sql @@ -0,0 +1,325 @@ +PRAGMA foreign_keys = ON; +create table `strapi_migrations` (`id` integer not null primary key autoincrement, `name` varchar(255), `time` datetime) +create table `strapi_database_schema` (`id` integer not null primary key autoincrement, `schema` json, `time` datetime, `hash` varchar(255)) +PRAGMA foreign_key_list (`strapi_migrations`); +PRAGMA foreign_key_list (`strapi_database_schema`); +PRAGMA foreign_keys = off; +BEGIN; +create table `strapi_core_store_settings` (`id` integer not null primary key autoincrement, `key` varchar(255) null, `value` text null, `type` varchar(255) null, `environment` varchar(255) null, `tag` varchar(255) null) +COMMIT; +BEGIN; +create table `strapi_webhooks` (`id` integer not null primary key autoincrement, `name` varchar(255) null, `url` text null, `headers` json null, `events` json null, `enabled` boolean null) +COMMIT; +BEGIN; +create table `admin_permissions` (`id` integer not null primary key autoincrement, `action` varchar(255) null, `action_parameters` json null, `subject` varchar(255) null, `properties` json null, `conditions` json null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `admin_permissions_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `admin_permissions_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `admin_permissions_created_by_id_fk` ON `admin_permissions` (`created_by_id`); +CREATE INDEX `admin_permissions_updated_by_id_fk` ON `admin_permissions` (`updated_by_id`); +COMMIT; +BEGIN; +create table `admin_users` (`id` integer not null primary key autoincrement, `firstname` varchar(255) null, `lastname` varchar(255) null, `username` varchar(255) null, `email` varchar(255) null, `password` varchar(255) null, `reset_password_token` varchar(255) null, `registration_token` varchar(255) null, `is_active` boolean null, `blocked` boolean null, `prefered_language` varchar(255) null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `admin_users_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `admin_users_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `admin_users_created_by_id_fk` ON `admin_users` (`created_by_id`); +CREATE INDEX `admin_users_updated_by_id_fk` ON `admin_users` (`updated_by_id`); +COMMIT; +BEGIN; +create table `admin_roles` (`id` integer not null primary key autoincrement, `name` varchar(255) null, `code` varchar(255) null, `description` varchar(255) null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `admin_roles_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `admin_roles_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `admin_roles_created_by_id_fk` ON `admin_roles` (`created_by_id`); +CREATE INDEX `admin_roles_updated_by_id_fk` ON `admin_roles` (`updated_by_id`); +COMMIT; +BEGIN; +create table `strapi_api_tokens` (`id` integer not null primary key autoincrement, `name` varchar(255) null, `description` varchar(255) null, `type` varchar(255) null, `access_key` varchar(255) null, `last_used_at` datetime null, `expires_at` datetime null, `lifespan` bigint null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `strapi_api_tokens_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `strapi_api_tokens_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `strapi_api_tokens_created_by_id_fk` ON `strapi_api_tokens` (`created_by_id`); +CREATE INDEX `strapi_api_tokens_updated_by_id_fk` ON `strapi_api_tokens` (`updated_by_id`); +COMMIT; +BEGIN; +create table `strapi_api_token_permissions` (`id` integer not null primary key autoincrement, `action` varchar(255) null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `strapi_api_token_permissions_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `strapi_api_token_permissions_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `strapi_api_token_permissions_created_by_id_fk` ON `strapi_api_token_permissions` (`created_by_id`); +CREATE INDEX `strapi_api_token_permissions_updated_by_id_fk` ON `strapi_api_token_permissions` (`updated_by_id`); +COMMIT; +BEGIN; +create table `strapi_transfer_tokens` (`id` integer not null primary key autoincrement, `name` varchar(255) null, `description` varchar(255) null, `access_key` varchar(255) null, `last_used_at` datetime null, `expires_at` datetime null, `lifespan` bigint null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `strapi_transfer_tokens_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `strapi_transfer_tokens_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `strapi_transfer_tokens_created_by_id_fk` ON `strapi_transfer_tokens` (`created_by_id`); +CREATE INDEX `strapi_transfer_tokens_updated_by_id_fk` ON `strapi_transfer_tokens` (`updated_by_id`); +COMMIT; +BEGIN; +create table `strapi_transfer_token_permissions` (`id` integer not null primary key autoincrement, `action` varchar(255) null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `strapi_transfer_token_permissions_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `strapi_transfer_token_permissions_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `strapi_transfer_token_permissions_created_by_id_fk` ON `strapi_transfer_token_permissions` (`created_by_id`); +CREATE INDEX `strapi_transfer_token_permissions_updated_by_id_fk` ON `strapi_transfer_token_permissions` (`updated_by_id`); +COMMIT; +BEGIN; +create table `files` (`id` integer not null primary key autoincrement, `name` varchar(255) null, `alternative_text` varchar(255) null, `caption` varchar(255) null, `width` integer null, `height` integer null, `formats` json null, `hash` varchar(255) null, `ext` varchar(255) null, `mime` varchar(255) null, `size` float null, `url` varchar(255) null, `preview_url` varchar(255) null, `provider` varchar(255) null, `provider_metadata` json null, `folder_path` varchar(255) null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `files_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `files_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `upload_files_folder_path_index` ON `files` (`folder_path`); +CREATE INDEX `upload_files_created_at_index` ON `files` (`created_at`); +CREATE INDEX `upload_files_updated_at_index` ON `files` (`updated_at`); +CREATE INDEX `upload_files_name_index` ON `files` (`name`); +CREATE INDEX `upload_files_size_index` ON `files` (`size`); +CREATE INDEX `upload_files_ext_index` ON `files` (`ext`); +CREATE INDEX `files_created_by_id_fk` ON `files` (`created_by_id`); +CREATE INDEX `files_updated_by_id_fk` ON `files` (`updated_by_id`); +COMMIT; +BEGIN; +create table `upload_folders` (`id` integer not null primary key autoincrement, `name` varchar(255) null, `path_id` integer null, `path` varchar(255) null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `upload_folders_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `upload_folders_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE UNIQUE INDEX `upload_folders_path_id_index` ON `upload_folders` (`path_id`); +CREATE UNIQUE INDEX `upload_folders_path_index` ON `upload_folders` (`path`); +CREATE INDEX `upload_folders_created_by_id_fk` ON `upload_folders` (`created_by_id`); +CREATE INDEX `upload_folders_updated_by_id_fk` ON `upload_folders` (`updated_by_id`); +COMMIT; +BEGIN; +create table `strapi_releases` (`id` integer not null primary key autoincrement, `name` varchar(255) null, `released_at` datetime null, `scheduled_at` datetime null, `timezone` varchar(255) null, `status` varchar(255) null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `strapi_releases_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `strapi_releases_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `strapi_releases_created_by_id_fk` ON `strapi_releases` (`created_by_id`); +CREATE INDEX `strapi_releases_updated_by_id_fk` ON `strapi_releases` (`updated_by_id`); +COMMIT; +BEGIN; +create table `strapi_release_actions` (`id` integer not null primary key autoincrement, `type` varchar(255) null, `target_id` integer null, `target_type` varchar(255) null, `content_type` varchar(255) null, `locale` varchar(255) null, `is_entry_valid` boolean null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `strapi_release_actions_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `strapi_release_actions_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `strapi_release_actions_created_by_id_fk` ON `strapi_release_actions` (`created_by_id`); +CREATE INDEX `strapi_release_actions_updated_by_id_fk` ON `strapi_release_actions` (`updated_by_id`); +COMMIT; +BEGIN; +create table `up_permissions` (`id` integer not null primary key autoincrement, `action` varchar(255) null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `up_permissions_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `up_permissions_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `up_permissions_created_by_id_fk` ON `up_permissions` (`created_by_id`); +CREATE INDEX `up_permissions_updated_by_id_fk` ON `up_permissions` (`updated_by_id`); +COMMIT; +BEGIN; +create table `up_roles` (`id` integer not null primary key autoincrement, `name` varchar(255) null, `description` varchar(255) null, `type` varchar(255) null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `up_roles_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `up_roles_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `up_roles_created_by_id_fk` ON `up_roles` (`created_by_id`); +CREATE INDEX `up_roles_updated_by_id_fk` ON `up_roles` (`updated_by_id`); +COMMIT; +BEGIN; +create table `up_users` (`id` integer not null primary key autoincrement, `username` varchar(255) null, `email` varchar(255) null, `provider` varchar(255) null, `password` varchar(255) null, `reset_password_token` varchar(255) null, `confirmation_token` varchar(255) null, `confirmed` boolean null, `blocked` boolean null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `up_users_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `up_users_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `up_users_created_by_id_fk` ON `up_users` (`created_by_id`); +CREATE INDEX `up_users_updated_by_id_fk` ON `up_users` (`updated_by_id`); +COMMIT; +BEGIN; +create table `i18n_locale` (`id` integer not null primary key autoincrement, `name` varchar(255) null, `code` varchar(255) null, `created_at` datetime null, `updated_at` datetime null, `created_by_id` integer null, `updated_by_id` integer null, constraint `i18n_locale_created_by_id_fk` foreign key(`created_by_id`) references `admin_users`(`id`) on delete SET NULL, constraint `i18n_locale_updated_by_id_fk` foreign key(`updated_by_id`) references `admin_users`(`id`) on delete SET NULL) +CREATE INDEX `i18n_locale_created_by_id_fk` ON `i18n_locale` (`created_by_id`); +CREATE INDEX `i18n_locale_updated_by_id_fk` ON `i18n_locale` (`updated_by_id`); +COMMIT; +BEGIN; +create table `admin_permissions_role_links` (`id` integer not null primary key autoincrement, `permission_id` integer null, `role_id` integer null, `permission_order` float null, constraint `admin_permissions_role_links_fk` foreign key(`permission_id`) references `admin_permissions`(`id`) on delete CASCADE, constraint `admin_permissions_role_links_inv_fk` foreign key(`role_id`) references `admin_roles`(`id`) on delete CASCADE) +CREATE INDEX `admin_permissions_role_links_fk` ON `admin_permissions_role_links` (`permission_id`); +CREATE INDEX `admin_permissions_role_links_inv_fk` ON `admin_permissions_role_links` (`role_id`); +CREATE UNIQUE INDEX `admin_permissions_role_links_unique` ON `admin_permissions_role_links` (`permission_id`, `role_id`); +CREATE INDEX `admin_permissions_role_links_order_inv_fk` ON `admin_permissions_role_links` (`permission_order`); +COMMIT; +BEGIN; +create table `admin_users_roles_links` (`id` integer not null primary key autoincrement, `user_id` integer null, `role_id` integer null, `role_order` float null, `user_order` float null, constraint `admin_users_roles_links_fk` foreign key(`user_id`) references `admin_users`(`id`) on delete CASCADE, constraint `admin_users_roles_links_inv_fk` foreign key(`role_id`) references `admin_roles`(`id`) on delete CASCADE) +CREATE INDEX `admin_users_roles_links_fk` ON `admin_users_roles_links` (`user_id`); +CREATE INDEX `admin_users_roles_links_inv_fk` ON `admin_users_roles_links` (`role_id`); +CREATE UNIQUE INDEX `admin_users_roles_links_unique` ON `admin_users_roles_links` (`user_id`, `role_id`); +CREATE INDEX `admin_users_roles_links_order_fk` ON `admin_users_roles_links` (`role_order`); +CREATE INDEX `admin_users_roles_links_order_inv_fk` ON `admin_users_roles_links` (`user_order`); +COMMIT; +BEGIN; +create table `strapi_api_token_permissions_token_links` (`id` integer not null primary key autoincrement, `api_token_permission_id` integer null, `api_token_id` integer null, `api_token_permission_order` float null, constraint `strapi_api_token_permissions_token_links_fk` foreign key(`api_token_permission_id`) references `strapi_api_token_permissions`(`id`) on delete CASCADE, constraint `strapi_api_token_permissions_token_links_inv_fk` foreign key(`api_token_id`) references `strapi_api_tokens`(`id`) on delete CASCADE) +CREATE INDEX `strapi_api_token_permissions_token_links_fk` ON `strapi_api_token_permissions_token_links` (`api_token_permission_id`); +CREATE INDEX `strapi_api_token_permissions_token_links_inv_fk` ON `strapi_api_token_permissions_token_links` (`api_token_id`); +CREATE UNIQUE INDEX `strapi_api_token_permissions_token_links_unique` ON `strapi_api_token_permissions_token_links` (`api_token_permission_id`, `api_token_id`); +CREATE INDEX `strapi_api_token_permissions_token_links_order_inv_fk` ON `strapi_api_token_permissions_token_links` (`api_token_permission_order`); +COMMIT; +BEGIN; +create table `strapi_transfer_token_permissions_token_links` (`id` integer not null primary key autoincrement, `transfer_token_permission_id` integer null, `transfer_token_id` integer null, `transfer_token_permission_order` float null, constraint `strapi_transfer_token_permissions_token_links_fk` foreign key(`transfer_token_permission_id`) references `strapi_transfer_token_permissions`(`id`) on delete CASCADE, constraint `strapi_transfer_token_permissions_token_links_inv_fk` foreign key(`transfer_token_id`) references `strapi_transfer_tokens`(`id`) on delete CASCADE) +CREATE INDEX `strapi_transfer_token_permissions_token_links_fk` ON `strapi_transfer_token_permissions_token_links` (`transfer_token_permission_id`); +CREATE INDEX `strapi_transfer_token_permissions_token_links_inv_fk` ON `strapi_transfer_token_permissions_token_links` (`transfer_token_id`); +CREATE UNIQUE INDEX `strapi_transfer_token_permissions_token_links_unique` ON `strapi_transfer_token_permissions_token_links` (`transfer_token_permission_id`, `transfer_token_id`); +CREATE INDEX `strapi_transfer_token_permissions_token_links_order_inv_fk` ON `strapi_transfer_token_permissions_token_links` (`transfer_token_permission_order`); +COMMIT; +BEGIN; +create table `files_related_morphs` (`id` integer not null primary key autoincrement, `file_id` integer null, `related_id` integer null, `related_type` varchar(255) null, `field` varchar(255) null, `order` float null, constraint `files_related_morphs_fk` foreign key(`file_id`) references `files`(`id`) on delete CASCADE) +CREATE INDEX `files_related_morphs_fk` ON `files_related_morphs` (`file_id`); +CREATE INDEX `files_related_morphs_order_index` ON `files_related_morphs` (`order`); +CREATE INDEX `files_related_morphs_id_column_index` ON `files_related_morphs` (`related_id`); +COMMIT; +BEGIN; +create table `files_folder_links` (`id` integer not null primary key autoincrement, `file_id` integer null, `folder_id` integer null, `file_order` float null, constraint `files_folder_links_fk` foreign key(`file_id`) references `files`(`id`) on delete CASCADE, constraint `files_folder_links_inv_fk` foreign key(`folder_id`) references `upload_folders`(`id`) on delete CASCADE) +CREATE INDEX `files_folder_links_fk` ON `files_folder_links` (`file_id`); +CREATE INDEX `files_folder_links_inv_fk` ON `files_folder_links` (`folder_id`); +CREATE UNIQUE INDEX `files_folder_links_unique` ON `files_folder_links` (`file_id`, `folder_id`); +CREATE INDEX `files_folder_links_order_inv_fk` ON `files_folder_links` (`file_order`); +COMMIT; +BEGIN; +create table `upload_folders_parent_links` (`id` integer not null primary key autoincrement, `folder_id` integer null, `inv_folder_id` integer null, `folder_order` float null, constraint `upload_folders_parent_links_fk` foreign key(`folder_id`) references `upload_folders`(`id`) on delete CASCADE, constraint `upload_folders_parent_links_inv_fk` foreign key(`inv_folder_id`) references `upload_folders`(`id`) on delete CASCADE) +CREATE INDEX `upload_folders_parent_links_fk` ON `upload_folders_parent_links` (`folder_id`); +CREATE INDEX `upload_folders_parent_links_inv_fk` ON `upload_folders_parent_links` (`inv_folder_id`); +CREATE UNIQUE INDEX `upload_folders_parent_links_unique` ON `upload_folders_parent_links` (`folder_id`, `inv_folder_id`); +CREATE INDEX `upload_folders_parent_links_order_inv_fk` ON `upload_folders_parent_links` (`folder_order`); +COMMIT; +BEGIN; +create table `strapi_release_actions_release_links` (`id` integer not null primary key autoincrement, `release_action_id` integer null, `release_id` integer null, `release_action_order` float null, constraint `strapi_release_actions_release_links_fk` foreign key(`release_action_id`) references `strapi_release_actions`(`id`) on delete CASCADE, constraint `strapi_release_actions_release_links_inv_fk` foreign key(`release_id`) references `strapi_releases`(`id`) on delete CASCADE) +CREATE INDEX `strapi_release_actions_release_links_fk` ON `strapi_release_actions_release_links` (`release_action_id`); +CREATE INDEX `strapi_release_actions_release_links_inv_fk` ON `strapi_release_actions_release_links` (`release_id`); +CREATE UNIQUE INDEX `strapi_release_actions_release_links_unique` ON `strapi_release_actions_release_links` (`release_action_id`, `release_id`); +CREATE INDEX `strapi_release_actions_release_links_order_inv_fk` ON `strapi_release_actions_release_links` (`release_action_order`); +COMMIT; +BEGIN; +create table `up_permissions_role_links` (`id` integer not null primary key autoincrement, `permission_id` integer null, `role_id` integer null, `permission_order` float null, constraint `up_permissions_role_links_fk` foreign key(`permission_id`) references `up_permissions`(`id`) on delete CASCADE, constraint `up_permissions_role_links_inv_fk` foreign key(`role_id`) references `up_roles`(`id`) on delete CASCADE) +CREATE INDEX `up_permissions_role_links_fk` ON `up_permissions_role_links` (`permission_id`); +CREATE INDEX `up_permissions_role_links_inv_fk` ON `up_permissions_role_links` (`role_id`); +CREATE UNIQUE INDEX `up_permissions_role_links_unique` ON `up_permissions_role_links` (`permission_id`, `role_id`); +CREATE INDEX `up_permissions_role_links_order_inv_fk` ON `up_permissions_role_links` (`permission_order`); +COMMIT; +BEGIN; +create table `up_users_role_links` (`id` integer not null primary key autoincrement, `user_id` integer null, `role_id` integer null, `user_order` float null, constraint `up_users_role_links_fk` foreign key(`user_id`) references `up_users`(`id`) on delete CASCADE, constraint `up_users_role_links_inv_fk` foreign key(`role_id`) references `up_roles`(`id`) on delete CASCADE) +CREATE INDEX `up_users_role_links_fk` ON `up_users_role_links` (`user_id`); +CREATE INDEX `up_users_role_links_inv_fk` ON `up_users_role_links` (`role_id`); +CREATE UNIQUE INDEX `up_users_role_links_unique` ON `up_users_role_links` (`user_id`, `role_id`); +CREATE INDEX `up_users_role_links_order_inv_fk` ON `up_users_role_links` (`user_order`); +COMMIT; +BEGIN; +COMMIT; +BEGIN; +COMMIT; +BEGIN; +SAVEPOINT trx32; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'admin_permissions'; +RELEASE trx32; +PRAGMA foreign_keys; +SAVEPOINT trx33; +CREATE TABLE `_knex_temp_alter079` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `action` varchar(255) NULL, `action_parameters` json NULL, `subject` varchar(255) NULL, `properties` json NULL, `conditions` json NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `admin_permissions_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_permissions_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_permissions_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +INSERT INTO "_knex_temp_alter079" SELECT * FROM "admin_permissions"; +DROP TABLE "admin_permissions"; +ALTER TABLE "_knex_temp_alter079" RENAME TO "admin_permissions"; +CREATE INDEX `admin_permissions_created_by_id_fk` ON `admin_permissions` (`created_by_id`); +CREATE INDEX `admin_permissions_updated_by_id_fk` ON `admin_permissions` (`updated_by_id`); +RELEASE trx33; +SAVEPOINT trx34; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'admin_permissions'; +RELEASE trx34; +PRAGMA foreign_keys; +SAVEPOINT trx35; +CREATE TABLE `_knex_temp_alter454` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `action` varchar(255) NULL, `action_parameters` json NULL, `subject` varchar(255) NULL, `properties` json NULL, `conditions` json NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `admin_permissions_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_permissions_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_permissions_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_permissions_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +INSERT INTO "_knex_temp_alter454" SELECT * FROM "admin_permissions"; +DROP TABLE "admin_permissions"; +ALTER TABLE "_knex_temp_alter454" RENAME TO "admin_permissions"; +CREATE INDEX `admin_permissions_created_by_id_fk` ON `admin_permissions` (`created_by_id`); +CREATE INDEX `admin_permissions_updated_by_id_fk` ON `admin_permissions` (`updated_by_id`); +RELEASE trx35; +COMMIT; +BEGIN; +SAVEPOINT trx37; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'admin_users'; +RELEASE trx37; +PRAGMA foreign_keys; +SAVEPOINT trx38; +CREATE TABLE `_knex_temp_alter752` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `firstname` varchar(255) NULL, `lastname` varchar(255) NULL, `username` varchar(255) NULL, `email` varchar(255) NULL, `password` varchar(255) NULL, `reset_password_token` varchar(255) NULL, `registration_token` varchar(255) NULL, `is_active` boolean NULL, `blocked` boolean NULL, `prefered_language` varchar(255) NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `admin_users_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_users_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_users_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +INSERT INTO "_knex_temp_alter752" SELECT * FROM "admin_users"; +DROP TABLE "admin_users"; +ALTER TABLE "_knex_temp_alter752" RENAME TO "admin_users"; +CREATE INDEX `admin_users_created_by_id_fk` ON `admin_users` (`created_by_id`); +CREATE INDEX `admin_users_updated_by_id_fk` ON `admin_users` (`updated_by_id`); +RELEASE trx38; +SAVEPOINT trx39; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'admin_users'; +RELEASE trx39; +PRAGMA foreign_keys; +SAVEPOINT trx40; +CREATE TABLE `_knex_temp_alter590` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `firstname` varchar(255) NULL, `lastname` varchar(255) NULL, `username` varchar(255) NULL, `email` varchar(255) NULL, `password` varchar(255) NULL, `reset_password_token` varchar(255) NULL, `registration_token` varchar(255) NULL, `is_active` boolean NULL, `blocked` boolean NULL, `prefered_language` varchar(255) NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `admin_users_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_users_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_users_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_users_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +INSERT INTO "_knex_temp_alter590" SELECT * FROM "admin_users"; +DROP TABLE "admin_users"; +ALTER TABLE "_knex_temp_alter590" RENAME TO "admin_users"; +CREATE INDEX `admin_users_created_by_id_fk` ON `admin_users` (`created_by_id`); +CREATE INDEX `admin_users_updated_by_id_fk` ON `admin_users` (`updated_by_id`); +RELEASE trx40; +COMMIT; +BEGIN; +SAVEPOINT trx42; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'admin_roles'; +RELEASE trx42; +PRAGMA foreign_keys; +SAVEPOINT trx43; +CREATE TABLE `_knex_temp_alter231` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `name` varchar(255) NULL, `code` varchar(255) NULL, `description` varchar(255) NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `admin_roles_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_roles_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_roles_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +INSERT INTO "_knex_temp_alter231" SELECT * FROM "admin_roles"; +DROP TABLE "admin_roles"; +ALTER TABLE "_knex_temp_alter231" RENAME TO "admin_roles"; +CREATE INDEX `admin_roles_created_by_id_fk` ON `admin_roles` (`created_by_id`); +CREATE INDEX `admin_roles_updated_by_id_fk` ON `admin_roles` (`updated_by_id`); +RELEASE trx43; +SAVEPOINT trx44; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'admin_roles'; +RELEASE trx44; +PRAGMA foreign_keys; +SAVEPOINT trx45; +CREATE TABLE `_knex_temp_alter768` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `name` varchar(255) NULL, `code` varchar(255) NULL, `description` varchar(255) NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `admin_roles_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_roles_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_roles_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `admin_roles_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +INSERT INTO "_knex_temp_alter768" SELECT * FROM "admin_roles"; +DROP TABLE "admin_roles"; +ALTER TABLE "_knex_temp_alter768" RENAME TO "admin_roles"; +CREATE INDEX `admin_roles_created_by_id_fk` ON `admin_roles` (`created_by_id`); +CREATE INDEX `admin_roles_updated_by_id_fk` ON `admin_roles` (`updated_by_id`); +RELEASE trx45; +COMMIT; +BEGIN; +SAVEPOINT trx47; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'strapi_api_tokens'; +RELEASE trx47; +PRAGMA foreign_keys; +SAVEPOINT trx48; +CREATE TABLE `_knex_temp_alter309` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `name` varchar(255) NULL, `description` varchar(255) NULL, `type` varchar(255) NULL, `access_key` varchar(255) NULL, `last_used_at` datetime NULL, `expires_at` datetime NULL, `lifespan` bigint NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `strapi_api_tokens_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_api_tokens_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_api_tokens_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +INSERT INTO "_knex_temp_alter309" SELECT * FROM "strapi_api_tokens"; +DROP TABLE "strapi_api_tokens"; +ALTER TABLE "_knex_temp_alter309" RENAME TO "strapi_api_tokens"; +CREATE INDEX `strapi_api_tokens_created_by_id_fk` ON `strapi_api_tokens` (`created_by_id`); +CREATE INDEX `strapi_api_tokens_updated_by_id_fk` ON `strapi_api_tokens` (`updated_by_id`); +RELEASE trx48; +SAVEPOINT trx49; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'strapi_api_tokens'; +RELEASE trx49; +PRAGMA foreign_keys; +SAVEPOINT trx50; +CREATE TABLE `_knex_temp_alter407` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `name` varchar(255) NULL, `description` varchar(255) NULL, `type` varchar(255) NULL, `access_key` varchar(255) NULL, `last_used_at` datetime NULL, `expires_at` datetime NULL, `lifespan` bigint NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `strapi_api_tokens_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_api_tokens_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_api_tokens_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_api_tokens_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +INSERT INTO "_knex_temp_alter407" SELECT * FROM "strapi_api_tokens"; +DROP TABLE "strapi_api_tokens"; +ALTER TABLE "_knex_temp_alter407" RENAME TO "strapi_api_tokens"; +CREATE INDEX `strapi_api_tokens_created_by_id_fk` ON `strapi_api_tokens` (`created_by_id`); +CREATE INDEX `strapi_api_tokens_updated_by_id_fk` ON `strapi_api_tokens` (`updated_by_id`); +RELEASE trx50; +COMMIT; +BEGIN; +SAVEPOINT trx52; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'strapi_api_token_permissions'; +RELEASE trx52; +PRAGMA foreign_keys; +SAVEPOINT trx53; +CREATE TABLE `_knex_temp_alter524` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `action` varchar(255) NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `strapi_api_token_permissions_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_api_token_permissions_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_api_token_permissions_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +INSERT INTO "_knex_temp_alter524" SELECT * FROM "strapi_api_token_permissions"; +DROP TABLE "strapi_api_token_permissions"; +ALTER TABLE "_knex_temp_alter524" RENAME TO "strapi_api_token_permissions"; +CREATE INDEX `strapi_api_token_permissions_created_by_id_fk` ON `strapi_api_token_permissions` (`created_by_id`); +CREATE INDEX `strapi_api_token_permissions_updated_by_id_fk` ON `strapi_api_token_permissions` (`updated_by_id`); +RELEASE trx53; +SAVEPOINT trx54; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'strapi_api_token_permissions'; +RELEASE trx54; +PRAGMA foreign_keys; +SAVEPOINT trx55; +CREATE TABLE `_knex_temp_alter412` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `action` varchar(255) NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `strapi_api_token_permissions_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_api_token_permissions_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_api_token_permissions_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_api_token_permissions_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +INSERT INTO "_knex_temp_alter412" SELECT * FROM "strapi_api_token_permissions"; +DROP TABLE "strapi_api_token_permissions"; +ALTER TABLE "_knex_temp_alter412" RENAME TO "strapi_api_token_permissions"; +CREATE INDEX `strapi_api_token_permissions_created_by_id_fk` ON `strapi_api_token_permissions` (`created_by_id`); +CREATE INDEX `strapi_api_token_permissions_updated_by_id_fk` ON `strapi_api_token_permissions` (`updated_by_id`); +RELEASE trx55; +COMMIT; +BEGIN; +SAVEPOINT trx57; +SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'strapi_transfer_tokens'; +RELEASE trx57; +PRAGMA foreign_keys; +SAVEPOINT trx58; +CREATE TABLE `_knex_temp_alter605` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `name` varchar(255) NULL, `description` varchar(255) NULL, `access_key` varchar(255) NULL, `last_used_at` datetime NULL, `expires_at` datetime NULL, `lifespan` bigint NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `strapi_transfer_tokens_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_transfer_tokens_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_transfer_tokens_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +--INSERT INTO "_knex_temp_alter605" SELECT * FROM "strapi_transfer_tokens"; +--DROP TABLE "strapi_transfer_tokens"; +--ALTER TABLE "_knex_temp_alter605" RENAME TO "strapi_transfer_tokens"; +--CREATE INDEX `strapi_transfer_tokens_created_by_id_fk` ON `strapi_transfer_tokens` (`created_by_id`); +--CREATE INDEX `strapi_transfer_tokens_updated_by_id_fk` ON `strapi_transfer_tokens` (`updated_by_id`); +--RELEASE trx58; +--SAVEPOINT trx59; +--SELECT type, sql FROM sqlite_master WHERE (type = 'table' OR (type = 'index' AND sql IS NOT NULL)) AND lower (tbl_name) = 'strapi_transfer_tokens'; +--RELEASE trx59; +--PRAGMA foreign_keys; +--SAVEPOINT trx60; +--CREATE TABLE `_knex_temp_alter855` (`id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `name` varchar(255) NULL, `description` varchar(255) NULL, `access_key` varchar(255) NULL, `last_used_at` datetime NULL, `expires_at` datetime NULL, `lifespan` bigint NULL, `created_at` datetime NULL, `updated_at` datetime NULL, `created_by_id` integer NULL, `updated_by_id` integer NULL, CONSTRAINT `strapi_transfer_tokens_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_transfer_tokens_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_transfer_tokens_created_by_id_fk` FOREIGN KEY (`created_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL, CONSTRAINT `strapi_transfer_tokens_updated_by_id_fk` FOREIGN KEY (`updated_by_id`) REFERENCES `admin_users` (`id`) ON DELETE SET NULL) +--INSERT INTO "_knex_temp_alter855" SELECT * FROM "strapi_transfer_tokens"; +--DROP TABLE "strapi_transfer_tokens"; +--ALTER TABLE "_knex_temp_alter855" RENAME TO "strapi_transfer_tokens"; +--CREATE INDEX `strapi_transfer_tokens_created_by_id_fk` ON `strapi_transfer_tokens` (`created_by_id`); +--CREATE INDEX `strapi_transfer_tokens_updated_by_id_fk` ON `strapi_transfer_tokens` (`updated_by_id`); +--RELEASE trx60; +COMMIT; diff --git a/libsql-server/src/auth/mod.rs b/libsql-server/src/auth/mod.rs index 09468f4b3a..26c6dfecc1 100644 --- a/libsql-server/src/auth/mod.rs +++ b/libsql-server/src/auth/mod.rs @@ -27,10 +27,7 @@ impl Auth { } } - pub fn authenticate( - &self, - context: Result, - ) -> Result { + pub fn authenticate(&self, context: UserAuthContext) -> Result { self.user_strategy.authenticate(context) } } diff --git a/libsql-server/src/auth/parsers.rs b/libsql-server/src/auth/parsers.rs index 643a3fee1b..dbae78cf17 100644 --- a/libsql-server/src/auth/parsers.rs +++ b/libsql-server/src/auth/parsers.rs @@ -1,4 +1,4 @@ -use crate::auth::{constants::GRPC_AUTH_HEADER, AuthError}; +use crate::auth::AuthError; use anyhow::{bail, Context as _, Result}; use axum::http::HeaderValue; @@ -36,12 +36,20 @@ pub fn parse_jwt_key(data: &str) -> Result { } } -pub(crate) fn parse_grpc_auth_header(metadata: &MetadataMap) -> Result { - metadata - .get(GRPC_AUTH_HEADER) - .ok_or(AuthError::AuthHeaderNotFound) - .and_then(|h| h.to_str().map_err(|_| AuthError::AuthHeaderNonAscii)) - .and_then(|t| UserAuthContext::from_auth_str(t)) +pub(crate) fn parse_grpc_auth_header( + metadata: &MetadataMap, + required_fields: &Vec, +) -> UserAuthContext { + let mut context = UserAuthContext::empty(); + for field in required_fields.iter() { + metadata + .get(field) + .map(|header| header.to_str().ok()) + .and_then(|r| r) + .map(|v| context.add_field(field.into(), v.into())); + } + + context } pub fn parse_http_auth_header<'a>( @@ -79,40 +87,26 @@ mod tests { #[test] fn parse_grpc_auth_header_returns_valid_context() { let mut map = tonic::metadata::MetadataMap::new(); - map.insert("x-authorization", "bearer 123".parse().unwrap()); - let context = parse_grpc_auth_header(&map).unwrap(); - assert_eq!(context.scheme().as_ref().unwrap(), "bearer"); - assert_eq!(context.token().as_ref().unwrap(), "123"); - } - - #[test] - fn parse_grpc_auth_header_error_no_header() { - let map = tonic::metadata::MetadataMap::new(); - let result = parse_grpc_auth_header(&map); + map.insert( + crate::auth::constants::GRPC_AUTH_HEADER, + "bearer 123".parse().unwrap(), + ); + let required_fields = vec!["x-authorization".into()]; + let context = parse_grpc_auth_header(&map, &required_fields); assert_eq!( - result.unwrap_err().to_string(), - "Expected authorization header but none given" + context.custom_fields.get("x-authorization"), + Some(&"bearer 123".to_string()) ); } - #[test] - fn parse_grpc_auth_header_error_non_ascii() { - let mut map = tonic::metadata::MetadataMap::new(); - map.insert("x-authorization", "bearer I❤NY".parse().unwrap()); - let result = parse_grpc_auth_header(&map); - assert_eq!(result.unwrap_err().to_string(), "Non-ASCII auth header") - } - - #[test] - fn parse_grpc_auth_header_error_malformed_auth_str() { - let mut map = tonic::metadata::MetadataMap::new(); - map.insert("x-authorization", "bearer123".parse().unwrap()); - let result = parse_grpc_auth_header(&map); - assert_eq!( - result.unwrap_err().to_string(), - "Auth string does not conform to ' ' form" - ) - } + // #[test] TODO rewrite + // fn parse_grpc_auth_header_error_non_ascii() { + // let mut map = tonic::metadata::MetadataMap::new(); + // map.insert("x-authorization", "bearer I❤NY".parse().unwrap()); + // let required_fields = Vec::new(); + // let result = parse_grpc_auth_header(&map, &required_fields); + // assert_eq!(result.unwrap_err().to_string(), "Non-ASCII auth header") + // } #[test] fn parse_http_auth_header_returns_auth_header_param_when_valid() { diff --git a/libsql-server/src/auth/user_auth_strategies/disabled.rs b/libsql-server/src/auth/user_auth_strategies/disabled.rs index b95d52c061..ef9aae9062 100644 --- a/libsql-server/src/auth/user_auth_strategies/disabled.rs +++ b/libsql-server/src/auth/user_auth_strategies/disabled.rs @@ -4,10 +4,7 @@ use crate::auth::{AuthError, Authenticated}; pub struct Disabled {} impl UserAuthStrategy for Disabled { - fn authenticate( - &self, - _context: Result, - ) -> Result { + fn authenticate(&self, _context: UserAuthContext) -> Result { tracing::trace!("executing disabled auth"); Ok(Authenticated::FullAccess) } @@ -26,7 +23,7 @@ mod tests { #[test] fn authenticates() { let strategy = Disabled::new(); - let context = Ok(UserAuthContext::empty()); + let context = UserAuthContext::empty(); assert!(matches!( strategy.authenticate(context).unwrap(), diff --git a/libsql-server/src/auth/user_auth_strategies/http_basic.rs b/libsql-server/src/auth/user_auth_strategies/http_basic.rs index fbb45d0912..2310c7821b 100644 --- a/libsql-server/src/auth/user_auth_strategies/http_basic.rs +++ b/libsql-server/src/auth/user_auth_strategies/http_basic.rs @@ -7,27 +7,31 @@ pub struct HttpBasic { } impl UserAuthStrategy for HttpBasic { - fn authenticate( - &self, - context: Result, - ) -> Result { + fn authenticate(&self, ctx: UserAuthContext) -> Result { tracing::trace!("executing http basic auth"); + let auth_str = None + .or_else(|| ctx.custom_fields.get("authorization")) + .or_else(|| ctx.custom_fields.get("x-authorization")); + + let (_, token) = auth_str + .ok_or(AuthError::AuthHeaderNotFound) + .map(|s| s.split_once(' ').ok_or(AuthError::AuthStringMalformed)) + .and_then(|o| o)?; // NOTE: this naive comparison may leak information about the `expected_value` // using a timing attack let expected_value = self.credential.trim_end_matches('='); - - let creds_match = match context?.token { - Some(s) => s.contains(expected_value), - None => expected_value.is_empty(), - }; - + let creds_match = token.contains(expected_value); if creds_match { return Ok(Authenticated::FullAccess); } Err(AuthError::BasicRejected) } + + fn required_fields(&self) -> Vec { + vec!["authorization".to_string(), "x-authorization".to_string()] + } } impl HttpBasic { @@ -48,7 +52,7 @@ mod tests { #[test] fn authenticates_with_valid_credential() { - let context = Ok(UserAuthContext::basic(CREDENTIAL)); + let context = UserAuthContext::basic(CREDENTIAL); assert!(matches!( strategy().authenticate(context).unwrap(), @@ -59,7 +63,7 @@ mod tests { #[test] fn authenticates_with_valid_trimmed_credential() { let credential = CREDENTIAL.trim_end_matches('='); - let context = Ok(UserAuthContext::basic(credential)); + let context = UserAuthContext::basic(credential); assert!(matches!( strategy().authenticate(context).unwrap(), @@ -69,7 +73,7 @@ mod tests { #[test] fn errors_when_credentials_do_not_match() { - let context = Ok(UserAuthContext::basic("abc")); + let context = UserAuthContext::basic("abc"); assert_eq!( strategy().authenticate(context).unwrap_err(), diff --git a/libsql-server/src/auth/user_auth_strategies/jwt.rs b/libsql-server/src/auth/user_auth_strategies/jwt.rs index da68e91df0..6fd504ba88 100644 --- a/libsql-server/src/auth/user_auth_strategies/jwt.rs +++ b/libsql-server/src/auth/user_auth_strategies/jwt.rs @@ -12,21 +12,16 @@ pub struct Jwt { } impl UserAuthStrategy for Jwt { - fn authenticate( - &self, - context: Result, - ) -> Result { + fn authenticate(&self, ctx: UserAuthContext) -> Result { tracing::trace!("executing jwt auth"); + let auth_str = None + .or_else(|| ctx.custom_fields.get("authorization")) + .or_else(|| ctx.custom_fields.get("x-authorization")) + .ok_or_else(|| AuthError::AuthHeaderNotFound)?; - let ctx = context?; - - let UserAuthContext { - scheme: Some(scheme), - token: Some(token), - } = ctx - else { - return Err(AuthError::HttpAuthHeaderInvalid); - }; + let (scheme, token) = auth_str + .split_once(' ') + .ok_or(AuthError::AuthStringMalformed)?; if !scheme.eq_ignore_ascii_case("bearer") { return Err(AuthError::HttpAuthHeaderUnsupportedScheme); @@ -34,6 +29,10 @@ impl UserAuthStrategy for Jwt { return validate_jwt(&self.key, &token); } + + fn required_fields(&self) -> Vec { + vec!["authentication".to_string()] + } } impl Jwt { @@ -155,7 +154,7 @@ mod tests { }; let token = encode(&token, &enc); - let context = Ok(UserAuthContext::bearer(token.as_str())); + let context = UserAuthContext::bearer(token.as_str()); assert!(matches!( strategy(dec).authenticate(context).unwrap(), @@ -177,8 +176,7 @@ mod tests { }; let token = encode(&token, &enc); - let context = Ok(UserAuthContext::bearer(token.as_str())); - + let context = UserAuthContext::bearer(token.as_str()); let Authenticated::Legacy(a) = strategy(dec).authenticate(context).unwrap() else { panic!() }; @@ -190,7 +188,7 @@ mod tests { #[test] fn errors_when_jwt_token_invalid() { let (_enc, dec) = key_pair(); - let context = Ok(UserAuthContext::bearer("abc")); + let context = UserAuthContext::bearer("abc"); assert_eq!( strategy(dec).authenticate(context).unwrap_err(), @@ -210,7 +208,7 @@ mod tests { let token = encode(&token, &enc); - let context = Ok(UserAuthContext::bearer(token.as_str())); + let context = UserAuthContext::bearer(token.as_str()); assert_eq!( strategy(dec).authenticate(context).unwrap_err(), @@ -232,7 +230,7 @@ mod tests { let token = encode(&token, &enc); - let context = Ok(UserAuthContext::bearer(token.as_str())); + let context = UserAuthContext::bearer(token.as_str()); let Authenticated::Authorized(a) = strategy(dec).authenticate(context).unwrap() else { panic!() diff --git a/libsql-server/src/auth/user_auth_strategies/mod.rs b/libsql-server/src/auth/user_auth_strategies/mod.rs index 4f0f2ef786..22dcfce224 100644 --- a/libsql-server/src/auth/user_auth_strategies/mod.rs +++ b/libsql-server/src/auth/user_auth_strategies/mod.rs @@ -3,6 +3,7 @@ pub mod http_basic; pub mod jwt; pub use disabled::Disabled; +use hashbrown::HashMap; pub use http_basic::HttpBasic; pub use jwt::Jwt; @@ -10,51 +11,31 @@ use super::{AuthError, Authenticated}; #[derive(Debug)] pub struct UserAuthContext { - scheme: Option, - token: Option, + pub custom_fields: HashMap, String>, // todo, add aliases } impl UserAuthContext { - pub fn scheme(&self) -> &Option { - &self.scheme - } - - pub fn token(&self) -> &Option { - &self.token - } - pub fn empty() -> UserAuthContext { UserAuthContext { - scheme: None, - token: None, + custom_fields: HashMap::new(), } } pub fn basic(creds: &str) -> UserAuthContext { UserAuthContext { - scheme: Some("Basic".into()), - token: Some(creds.into()), + custom_fields: HashMap::from([("authorization".into(), format!("Basic {creds}"))]), } } pub fn bearer(token: &str) -> UserAuthContext { UserAuthContext { - scheme: Some("Bearer".into()), - token: Some(token.into()), - } - } - - pub fn bearer_opt(token: Option) -> UserAuthContext { - UserAuthContext { - scheme: Some("Bearer".into()), - token: token, + custom_fields: HashMap::from([("authorization".into(), format!("Bearer {token}"))]), } } pub fn new(scheme: &str, token: &str) -> UserAuthContext { UserAuthContext { - scheme: Some(scheme.into()), - token: Some(token.into()), + custom_fields: HashMap::from([("authorization".into(), format!("{scheme} {token}"))]), } } @@ -64,11 +45,16 @@ impl UserAuthContext { .ok_or(AuthError::AuthStringMalformed)?; Ok(UserAuthContext::new(scheme, token)) } + + pub fn add_field(&mut self, key: String, value: String) { + self.custom_fields.insert(key.into(), value.into()); + } } pub trait UserAuthStrategy: Sync + Send { - fn authenticate( - &self, - context: Result, - ) -> Result; + fn required_fields(&self) -> Vec { + vec![] + } + + fn authenticate(&self, context: UserAuthContext) -> Result; } diff --git a/libsql-server/src/hrana/ws/conn.rs b/libsql-server/src/hrana/ws/conn.rs index e34e00e6bf..a5581cbc71 100644 --- a/libsql-server/src/hrana/ws/conn.rs +++ b/libsql-server/src/hrana/ws/conn.rs @@ -206,16 +206,18 @@ async fn handle_client_msg(conn: &mut Conn, client_msg: proto::ClientMsg) -> Res } async fn handle_hello_msg(conn: &mut Conn, jwt: Option) -> Result { - let hello_res = match conn.session.as_mut() { - None => { - session::handle_initial_hello(&conn.server, conn.version, jwt, conn.namespace.clone()) - .await - .map(|session| conn.session = Some(session)) - } - Some(session) => { - session::handle_repeated_hello(&conn.server, session, jwt, conn.namespace.clone()).await - } - }; + let auth = session::handle_hello(&conn.server, jwt, conn.namespace.clone()).await; + + let hello_res = auth + .map(|a| { + if let Some(sess) = conn.session.as_mut() { + sess.update_auth(a) + } else { + conn.session = Some(session::Session::new(a, conn.version)); + Ok(()) + } + }) + .and_then(|o| o); match hello_res { Ok(_) => { diff --git a/libsql-server/src/hrana/ws/session.rs b/libsql-server/src/hrana/ws/session.rs index aef1f63574..da2173d849 100644 --- a/libsql-server/src/hrana/ws/session.rs +++ b/libsql-server/src/hrana/ws/session.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use std::sync::Arc; -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, bail, Error, Result}; use futures::future::BoxFuture; use tokio::sync::{mpsc, oneshot}; @@ -22,6 +22,29 @@ pub struct Session { cursors: HashMap, } +impl Session { + pub fn new(auth: Authenticated, version: Version) -> Self { + Self { + auth, + version, + streams: HashMap::new(), + sqls: HashMap::new(), + cursors: HashMap::new(), + } + } + + pub fn update_auth(&mut self, auth: Authenticated) -> Result<(), Error> { + if self.version < Version::Hrana2 { + bail!(ProtocolError::NotSupported { + what: "Repeated hello message", + min_version: Version::Hrana2, + }) + } + self.auth = auth; + Ok(()) + } +} + struct StreamHandle { job_tx: mpsc::Sender, cursor_id: Option, @@ -65,60 +88,35 @@ pub enum ResponseError { Batch(batch::BatchError), } -pub(super) async fn handle_initial_hello( +pub(super) async fn handle_hello( server: &Server, - version: Version, jwt: Option, namespace: NamespaceName, -) -> Result { - // todo dupe #auth +) -> Result { let namespace_jwt_key = server .namespaces .with(namespace.clone(), |ns| ns.jwt_key()) .await??; - let auth = namespace_jwt_key + let auth_strategy = namespace_jwt_key .map(Jwt::new) .map(Auth::new) - .unwrap_or(server.user_auth_strategy.clone()) - .authenticate(Ok(UserAuthContext::bearer_opt(jwt))) - .map_err(|err| anyhow!(ResponseError::Auth { source: err }))?; - - Ok(Session { - auth, - version, - streams: HashMap::new(), - sqls: HashMap::new(), - cursors: HashMap::new(), - }) -} + .unwrap_or_else(|| server.user_auth_strategy.clone()); -pub(super) async fn handle_repeated_hello( - server: &Server, - session: &mut Session, - jwt: Option, - namespace: NamespaceName, -) -> Result<()> { - if session.version < Version::Hrana2 { - bail!(ProtocolError::NotSupported { - what: "Repeated hello message", - min_version: Version::Hrana2, - }) - } - // todo dupe #auth - let namespace_jwt_key = server - .namespaces - .with(namespace.clone(), |ns| ns.jwt_key()) - .await??; + let context: UserAuthContext = + build_context(jwt, &auth_strategy.user_strategy.required_fields()); - session.auth = namespace_jwt_key - .map(Jwt::new) - .map(Auth::new) - .unwrap_or_else(|| server.user_auth_strategy.clone()) - .authenticate(Ok(UserAuthContext::bearer_opt(jwt))) - .map_err(|err| anyhow!(ResponseError::Auth { source: err }))?; + auth_strategy + .authenticate(context) + .map_err(|err| anyhow!(ResponseError::Auth { source: err })) +} - Ok(()) +fn build_context(jwt: Option, required_fields: &Vec) -> UserAuthContext { + let mut ctx = UserAuthContext::empty(); + if required_fields.contains(&"authorization".into()) && jwt.is_some() { + ctx.add_field("authorization".into(), jwt.unwrap()); + } + ctx } pub(super) async fn handle_request( diff --git a/libsql-server/src/http/user/db_factory.rs b/libsql-server/src/http/user/db_factory.rs index 257d8811c1..794a1f25ac 100644 --- a/libsql-server/src/http/user/db_factory.rs +++ b/libsql-server/src/http/user/db_factory.rs @@ -3,6 +3,7 @@ use std::sync::Arc; use axum::extract::{FromRequestParts, Path}; use hyper::http::request::Parts; use hyper::HeaderMap; +use libsql_replication::rpc::replication::NAMESPACE_METADATA_KEY; use crate::auth::Authenticated; use crate::connection::MakeConnection; @@ -46,18 +47,25 @@ pub fn namespace_from_headers( return Ok(NamespaceName::default()); } - let host = headers - .get("host") - .ok_or_else(|| Error::InvalidHost("missing host header".into()))? - .as_bytes(); - let host_str = std::str::from_utf8(host) - .map_err(|_| Error::InvalidHost("host header is not valid UTF-8".into()))?; + headers + .get(NAMESPACE_METADATA_KEY) + .ok_or(Error::InvalidNamespace) + .and_then(|h| h.to_str().map_err(|_| Error::InvalidNamespace)) + .and_then(|n| NamespaceName::from_string(n.into())) + .or_else(|_| { + let host = headers + .get("host") + .ok_or_else(|| Error::InvalidHost("missing host header".into()))? + .as_bytes(); + let host_str = std::str::from_utf8(host) + .map_err(|_| Error::InvalidHost("host header is not valid UTF-8".into()))?; - match split_namespace(host_str) { - Ok(ns) => Ok(ns), - Err(_) if !disable_default_namespace => Ok(NamespaceName::default()), - Err(e) => Err(e), - } + match split_namespace(host_str) { + Ok(ns) => Ok(ns), + Err(_) if !disable_default_namespace => Ok(NamespaceName::default()), + Err(e) => Err(e), + } + }) } pub struct MakeConnectionExtractorPath(pub Arc>); diff --git a/libsql-server/src/http/user/extract.rs b/libsql-server/src/http/user/extract.rs index b850e76ff3..8ab1a489d5 100644 --- a/libsql-server/src/http/user/extract.rs +++ b/libsql-server/src/http/user/extract.rs @@ -1,7 +1,7 @@ use axum::extract::FromRequestParts; use crate::{ - auth::{Auth, AuthError, Jwt, UserAuthContext}, + auth::{Auth, Jwt}, connection::RequestContext, }; @@ -26,21 +26,15 @@ impl FromRequestParts for RequestContext { .with(namespace.clone(), |ns| ns.jwt_key()) .await??; - let context = parts - .headers - .get(hyper::header::AUTHORIZATION) - .ok_or(AuthError::AuthHeaderNotFound) - .and_then(|h| h.to_str().map_err(|_| AuthError::AuthHeaderNonAscii)) - .and_then(|t| UserAuthContext::from_auth_str(t)); - - let authenticated = namespace_jwt_key + let auth = namespace_jwt_key .map(Jwt::new) .map(Auth::new) - .unwrap_or_else(|| state.user_auth_strategy.clone()) - .authenticate(context)?; + .unwrap_or_else(|| state.user_auth_strategy.clone()); + + let context = super::build_context(&parts.headers, &auth.user_strategy.required_fields()); Ok(Self::new( - authenticated, + auth.authenticate(context)?, namespace, state.namespaces.meta_store().clone(), )) diff --git a/libsql-server/src/http/user/mod.rs b/libsql-server/src/http/user/mod.rs index 3e394fc579..26b93e2de7 100644 --- a/libsql-server/src/http/user/mod.rs +++ b/libsql-server/src/http/user/mod.rs @@ -468,20 +468,36 @@ impl FromRequestParts for Authenticated { .with(ns.clone(), |ns| ns.jwt_key()) .await??; - let context = parts - .headers - .get(hyper::header::AUTHORIZATION) - .ok_or(AuthError::AuthHeaderNotFound) - .and_then(|h| h.to_str().map_err(|_| AuthError::AuthHeaderNonAscii)) - .and_then(|t| UserAuthContext::from_auth_str(t)); - - let authenticated = namespace_jwt_key + let auth = namespace_jwt_key .map(Jwt::new) .map(Auth::new) - .unwrap_or_else(|| state.user_auth_strategy.clone()) - .authenticate(context)?; - Ok(authenticated) + .unwrap_or_else(|| state.user_auth_strategy.clone()); + + let context = build_context(&parts.headers, &auth.user_strategy.required_fields()); + + Ok(auth.authenticate(context)?) + } +} + +fn build_context( + headers: &hyper::HeaderMap, + required_fields: &Vec, +) -> UserAuthContext { + let mut ctx = headers + .get(hyper::header::AUTHORIZATION) + .ok_or(AuthError::AuthHeaderNotFound) + .and_then(|h| h.to_str().map_err(|_| AuthError::AuthHeaderNonAscii)) + .and_then(|t| UserAuthContext::from_auth_str(t)) + .unwrap_or(UserAuthContext::empty()); + + for field in required_fields.iter() { + headers + .get(field) + .map(|h| h.to_str().ok()) + .and_then(|t| t.map(|s| ctx.add_field(field.into(), s.into()))); } + + ctx } impl FromRef for Auth { diff --git a/libsql-server/src/replication/primary/logger.rs b/libsql-server/src/replication/primary/logger.rs index 85ff65dfe7..165925f63b 100644 --- a/libsql-server/src/replication/primary/logger.rs +++ b/libsql-server/src/replication/primary/logger.rs @@ -445,14 +445,16 @@ fn atomic_rename(p1: impl AsRef, p2: impl AsRef) -> anyhow::Result<( use nix::libc::renamex_np; use nix::libc::RENAME_SWAP; - let p1 = CString::new(p1.as_ref().as_os_str().as_bytes())?; - let p2 = CString::new(p2.as_ref().as_os_str().as_bytes())?; + let cp1 = CString::new(p1.as_ref().as_os_str().as_bytes())?; + let cp2 = CString::new(p2.as_ref().as_os_str().as_bytes())?; unsafe { - let ret = renamex_np(p1.as_ptr(), p2.as_ptr(), RENAME_SWAP); + let ret = renamex_np(cp1.as_ptr(), cp2.as_ptr(), RENAME_SWAP); if ret != 0 { bail!( - "failed to perform snapshot file swap: {ret}, errno: {}", + "failed to perform snapshot file swap {} -> {}: {ret}, errno: {}", + p1.as_ref().display(), + p2.as_ref().display(), std::io::Error::last_os_error() ); } @@ -473,7 +475,13 @@ fn atomic_rename(p1: impl AsRef, p2: impl AsRef) -> anyhow::Result<( p2.as_ref(), RenameFlags::RENAME_EXCHANGE, ) - .context("failed to perform snapshot file swap")?; + .with_context(|| { + format!( + "failed to perform snapshot file swap {} -> {}", + p1.as_ref().display(), + p2.as_ref().display() + ) + })?; Ok(()) } diff --git a/libsql-server/src/rpc/proxy.rs b/libsql-server/src/rpc/proxy.rs index d7f96d68a7..aba85c64eb 100644 --- a/libsql-server/src/rpc/proxy.rs +++ b/libsql-server/src/rpc/proxy.rs @@ -335,7 +335,8 @@ impl ProxyService { }; let auth = if let Some(auth) = auth { - let context = parse_grpc_auth_header(req.metadata()); + let context = + parse_grpc_auth_header(req.metadata(), &auth.user_strategy.required_fields()); auth.authenticate(context)? } else { Authenticated::from_proxy_grpc_request(req)? diff --git a/libsql-server/src/rpc/replica_proxy.rs b/libsql-server/src/rpc/replica_proxy.rs index 6945cfffed..47732daba7 100644 --- a/libsql-server/src/rpc/replica_proxy.rs +++ b/libsql-server/src/rpc/replica_proxy.rs @@ -45,7 +45,7 @@ impl ReplicaProxyService { let namespace_jwt_key = jwt_result.and_then(|s| s); - let auth_strategy = match namespace_jwt_key { + let auth = match namespace_jwt_key { Ok(Some(key)) => Ok(Auth::new(Jwt::new(key))), Ok(None) | Err(crate::error::Error::NamespaceDoesntExist(_)) => { Ok(self.user_auth_strategy.clone()) @@ -56,10 +56,9 @@ impl ReplicaProxyService { ))), }?; - let auth_context = parse_grpc_auth_header(req.metadata()); - auth_strategy - .authenticate(auth_context)? - .upgrade_grpc_request(req); + let auth_context = + parse_grpc_auth_header(req.metadata(), &auth.user_strategy.required_fields()); + auth.authenticate(auth_context)?.upgrade_grpc_request(req); return Ok(()); } } diff --git a/libsql-server/src/rpc/replication_log.rs b/libsql-server/src/rpc/replication_log.rs index 5a93b3331e..4dc72190bd 100644 --- a/libsql-server/src/rpc/replication_log.rs +++ b/libsql-server/src/rpc/replication_log.rs @@ -94,8 +94,9 @@ impl ReplicationLogService { }; if let Some(auth) = auth { - let user_credential = parse_grpc_auth_header(req.metadata()); - auth.authenticate(user_credential)?; + let context = + parse_grpc_auth_header(req.metadata(), &auth.user_strategy.required_fields()); + auth.authenticate(context)?; } Ok(()) diff --git a/libsql-server/tests/cluster/mod.rs b/libsql-server/tests/cluster/mod.rs index 688391a7a8..1171d4a5d0 100644 --- a/libsql-server/tests/cluster/mod.rs +++ b/libsql-server/tests/cluster/mod.rs @@ -212,6 +212,23 @@ fn sync_many_replica() { )); } + let client = Client::new(); + + let stats = client + .get("http://primary:9090/v1/namespaces/default/stats") + .await? + .json_value() + .await + .unwrap(); + + let stat = stats + .get("embedded_replica_frames_replicated") + .unwrap() + .as_u64() + .unwrap(); + + assert_eq!(stat, 0); + Ok(()) }); diff --git a/libsql-server/tests/embedded_replica/mod.rs b/libsql-server/tests/embedded_replica/mod.rs index 868bf3d1ea..6fe50c9ef7 100644 --- a/libsql-server/tests/embedded_replica/mod.rs +++ b/libsql-server/tests/embedded_replica/mod.rs @@ -983,3 +983,55 @@ fn errors_on_bad_replica() { sim.run().unwrap(); } + +#[test] +fn malformed_database() { + let mut sim = Builder::new() + .simulation_duration(Duration::from_secs(u64::MAX)) + .build(); + + let tmp_embedded = tempdir().unwrap(); + let tmp_host = tempdir().unwrap(); + let tmp_embedded_path = tmp_embedded.path().to_owned(); + let tmp_host_path = tmp_host.path().to_owned(); + + make_primary(&mut sim, tmp_host_path.clone()); + + sim.client("client", async move { + let client = Client::new(); + client + .post("http://primary:9090/v1/namespaces/foo/create", json!({})) + .await?; + + let path = tmp_embedded_path.join("embedded"); + let db = libsql::Builder::new_remote_replica( + path.to_str().unwrap(), + "http://foo.primary:8080".to_string(), + "".to_string(), + ) + .read_your_writes(true) + .connector(TurmoilConnector) + .build() + .await?; + + let conn = db.connect()?; + + let dir = env!("CARGO_MANIFEST_DIR").to_string(); + + let file = std::fs::read_to_string(dir + "/output.sql").unwrap(); + + let sqls = file.lines(); + + for sql in sqls { + if !sql.starts_with("--") { + conn.execute(sql, ()).await.unwrap(); + } + } + + db.sync().await.unwrap(); + + Ok(()) + }); + + sim.run().unwrap(); +} diff --git a/libsql/examples/remote_sync.rs b/libsql/examples/remote_sync.rs index ccc31eb033..d42ccd1f75 100644 --- a/libsql/examples/remote_sync.rs +++ b/libsql/examples/remote_sync.rs @@ -7,40 +7,42 @@ async fn main() { tracing_subscriber::fmt::init(); // The local database path where the data will be stored. - let db_path = match std::env::var("LIBSQL_DB_PATH") { - Ok(path) => path, - Err(_) => { + let db_path = std::env::var("LIBSQL_DB_PATH") + .map_err(|_| { eprintln!( "Please set the LIBSQL_DB_PATH environment variable to set to local database path." - ); - return; - } - }; + ) + }) + .unwrap(); // The remote sync URL to use. - let sync_url = match std::env::var("LIBSQL_SYNC_URL") { - Ok(url) => url, - Err(_) => { + let sync_url = std::env::var("LIBSQL_SYNC_URL") + .map_err(|_| { eprintln!( "Please set the LIBSQL_SYNC_URL environment variable to set to remote sync URL." - ); - return; - } - }; + ) + }) + .unwrap(); + + let namespace = std::env::var("LIBSQL_NAMESPACE").ok(); // The authentication token to use. let auth_token = std::env::var("LIBSQL_AUTH_TOKEN").unwrap_or("".to_string()); - let db = match Builder::new_remote_replica(db_path, sync_url, auth_token) - .build() - .await - { + let db_builder = if let Some(ns) = namespace { + Builder::new_remote_replica(db_path, sync_url, auth_token).namespace(&ns) + } else { + Builder::new_remote_replica(db_path, sync_url, auth_token) + }; + + let db = match db_builder.build().await { Ok(db) => db, Err(error) => { eprintln!("Error connecting to remote sync server: {}", error); return; } }; + let conn = db.connect().unwrap(); print!("Syncing with remote database..."); diff --git a/libsql/src/database.rs b/libsql/src/database.rs index dd5ebc671f..6659b9ba19 100644 --- a/libsql/src/database.rs +++ b/libsql/src/database.rs @@ -184,7 +184,7 @@ cfg_replication! { None, OpenFlags::default(), encryption_config.clone(), - None + None, ).await?; Ok(Database { @@ -309,6 +309,7 @@ cfg_replication! { read_your_writes, encryption_config.clone(), sync_interval, + None, None ).await?; diff --git a/libsql/src/database/builder.rs b/libsql/src/database/builder.rs index 065b50588c..52f191092e 100644 --- a/libsql/src/database/builder.rs +++ b/libsql/src/database/builder.rs @@ -60,7 +60,8 @@ impl Builder<()> { encryption_config: None, read_your_writes: true, sync_interval: None, - http_request_callback: None + http_request_callback: None, + namespace: None }, } } @@ -165,6 +166,7 @@ cfg_replication! { read_your_writes: bool, sync_interval: Option, http_request_callback: Option, + namespace: Option, } /// Local replica configuration type in [`Builder`]. @@ -226,6 +228,12 @@ cfg_replication! { } + pub fn namespace(mut self, namespace: T) -> Builder + where T: Into{ + self.inner.namespace = Some(namespace.into()); + self + } + #[doc(hidden)] pub fn version(mut self, version: String) -> Builder { self.inner.remote = self.inner.remote.version(version); @@ -246,7 +254,8 @@ cfg_replication! { encryption_config, read_your_writes, sync_interval, - http_request_callback + http_request_callback, + namespace } = self.inner; let connector = if let Some(connector) = connector { @@ -273,7 +282,8 @@ cfg_replication! { read_your_writes, encryption_config.clone(), sync_interval, - http_request_callback + http_request_callback, + namespace, ) .await?; @@ -339,7 +349,7 @@ cfg_replication! { version, flags, encryption_config.clone(), - http_request_callback + http_request_callback, ) .await? } else { diff --git a/libsql/src/local/database.rs b/libsql/src/local/database.rs index 1ffdc49b12..712c9e095a 100644 --- a/libsql/src/local/database.rs +++ b/libsql/src/local/database.rs @@ -65,6 +65,7 @@ impl Database { encryption_config, sync_interval, None, + None, ) .await } @@ -81,6 +82,7 @@ impl Database { encryption_config: Option, sync_interval: Option, http_request_callback: Option, + namespace: Option ) -> Result { use std::path::PathBuf; @@ -95,6 +97,7 @@ impl Database { auth_token, version.as_deref(), http_request_callback, + namespace, ) .unwrap(); let path = PathBuf::from(db_path); @@ -166,6 +169,7 @@ impl Database { auth_token, version.as_deref(), http_request_callback, + None, ) .unwrap(); diff --git a/libsql/src/replication/client.rs b/libsql/src/replication/client.rs index 8ef5edaf04..8b43ce0316 100644 --- a/libsql/src/replication/client.rs +++ b/libsql/src/replication/client.rs @@ -47,6 +47,7 @@ impl Client { auth_token: impl AsRef, version: Option<&str>, http_request_callback: Option, + maybe_namespace: Option, ) -> anyhow::Result { let ver = version.unwrap_or(env!("CARGO_PKG_VERSION")); @@ -58,7 +59,15 @@ impl Client { .try_into() .context("Invalid auth token must be ascii")?; - let ns = split_namespace(origin.host().unwrap()).unwrap_or_else(|_| "default".to_string()); + let ns = if let Some(ns_from_arg) = maybe_namespace + { + ns_from_arg + } else if let Ok(ns_from_host) = split_namespace(origin.host().unwrap()) { + ns_from_host + } else { + "default".to_string() + }; + let namespace = BinaryMetadataValue::from_bytes(ns.as_bytes()); let channel = GrpcChannel::new(connector, http_request_callback); @@ -124,7 +133,7 @@ impl GrpcChannel { connector: ConnectorService, http_request_callback: Option, ) -> Self { - let client = hyper::Client::builder().build(connector); + let client = hyper::Client::builder().pool_idle_timeout(None).pool_max_idle_per_host(3).build(connector); let client = GrpcWebClientService::new(client); let classifier = GrpcErrorsAsFailures::new().with_success(GrpcCode::FailedPrecondition);